├── .gitignore ├── LICENSE ├── README.md ├── api ├── .idea │ ├── .name │ ├── artifacts │ │ ├── scd.xml │ │ └── soundcloud_downloader_war_exploded.xml │ ├── dictionaries │ │ └── theapache64.xml │ ├── encodings.xml │ ├── misc.xml │ ├── modules.xml │ ├── uiDesigner.xml │ └── vcs.xml ├── extras │ └── database.sql ├── soundcloud_downloader.iml ├── src │ └── com │ │ └── theah64 │ │ └── scd │ │ ├── Lab.java │ │ ├── core │ │ └── SoundCloudDownloader.java │ │ ├── database │ │ ├── Connection.java │ │ └── tables │ │ │ ├── BaseTable.java │ │ │ ├── DownloadRequests.java │ │ │ ├── Preference.java │ │ │ ├── Requests.java │ │ │ ├── SCClients.java │ │ │ ├── Tracks.java │ │ │ └── Users.java │ │ ├── models │ │ ├── DownloadRequest.java │ │ ├── JSONTracks.java │ │ ├── Request.java │ │ ├── SCClient.java │ │ ├── Track.java │ │ └── User.java │ │ ├── servlets │ │ ├── AdvancedBaseServlet.java │ │ ├── DirectDownloaderServlet.java │ │ ├── DownloadAPKServlet.java │ │ ├── DownloaderServlet.java │ │ ├── INServlet.java │ │ └── TracksServlet.java │ │ └── utils │ │ ├── APIResponse.java │ │ ├── FileNameUtils.java │ │ ├── HeaderSecurity.java │ │ ├── MailHelper.java │ │ ├── NetworkHelper.java │ │ └── Request.java └── web │ ├── WEB-INF │ └── web.xml │ ├── jaan_kesi.mp3 │ └── status.jsp ├── client ├── .gitignore ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ └── Project.xml │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── dictionaries │ │ └── theapache64.xml │ ├── gradle.xml │ ├── inspectionProfiles │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── markdown-navigator │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── google-services.json │ ├── proguard-rules.pro │ ├── release │ │ ├── app-release.apk │ │ └── output.json │ ├── soundclouddownloader.apk │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── theah64 │ │ │ └── musicdog │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── database.sql │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── com │ │ │ │ └── theah64 │ │ │ │ └── soundclouddownloader │ │ │ │ ├── adapters │ │ │ │ ├── ITSAdapter.java │ │ │ │ ├── PlaylistDownloadAdapter.java │ │ │ │ └── TracksAndPlaylistsViewPagerAdapter.java │ │ │ │ ├── database │ │ │ │ ├── BaseTable.java │ │ │ │ ├── Playlists.java │ │ │ │ └── Tracks.java │ │ │ │ ├── interfaces │ │ │ │ ├── MainActivityCallback.java │ │ │ │ ├── PlaylistListener.java │ │ │ │ └── TrackListener.java │ │ │ │ ├── models │ │ │ │ ├── ITSNode.java │ │ │ │ ├── Playlist.java │ │ │ │ └── Track.java │ │ │ │ ├── receivers │ │ │ │ ├── OnBootCompleted.java │ │ │ │ └── OnDownloadFinishedReceiver.java │ │ │ │ ├── services │ │ │ │ ├── ClipboardWatchIgniterService.java │ │ │ │ └── DownloaderService.java │ │ │ │ ├── ui │ │ │ │ ├── activities │ │ │ │ │ ├── BaseAppCompatActivity.java │ │ │ │ │ ├── DownloaderActivity.java │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ ├── PlaylistDownloadActivity.java │ │ │ │ │ ├── PlaylistTracksActivity.java │ │ │ │ │ ├── SplashActivity.java │ │ │ │ │ ├── TestActivity.java │ │ │ │ │ └── settings │ │ │ │ │ │ ├── SettingsActivity.java │ │ │ │ │ │ └── SettingsActivityCompat.java │ │ │ │ └── fragments │ │ │ │ │ ├── BaseMusicFragment.java │ │ │ │ │ ├── PlaylistsFragment.java │ │ │ │ │ └── TracksFragment.java │ │ │ │ ├── utils │ │ │ │ ├── APIRequestBuilder.java │ │ │ │ ├── APIRequestGateway.java │ │ │ │ ├── APIResponse.java │ │ │ │ ├── App.java │ │ │ │ ├── ClipboardUtils.java │ │ │ │ ├── CommonUtils.java │ │ │ │ ├── DarKnight.java │ │ │ │ ├── DownloadIgniter.java │ │ │ │ ├── DownloadUtils.java │ │ │ │ ├── FileUtils.java │ │ │ │ ├── InputDialogUtils.java │ │ │ │ ├── NetworkUtils.java │ │ │ │ ├── OkHttpUtils.java │ │ │ │ ├── PrefUtils.java │ │ │ │ ├── ProfileUtils.java │ │ │ │ ├── Random.java │ │ │ │ ├── SingletonToast.java │ │ │ │ └── UriCompat.java │ │ │ │ └── widgets │ │ │ │ ├── AdvancedEditText.java │ │ │ │ ├── ThemedSnackbar.java │ │ │ │ └── ValidTextInputLayout.java │ │ └── res │ │ │ ├── anim │ │ │ └── blink.xml │ │ │ ├── drawable-hdpi-v11 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-hdpi-v9 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-hdpi │ │ │ ├── ic_add_white_24dp.png │ │ │ ├── ic_arrow_back_black_24dp.png │ │ │ ├── ic_av_album_70dp.png │ │ │ ├── ic_check_box_orange_24dp.png │ │ │ ├── ic_check_box_outline_orange_24dp.png │ │ │ ├── ic_file_download_white_24dp.png │ │ │ ├── ic_logo_24dp.png │ │ │ ├── ic_logo_no_bg_100dp.png │ │ │ ├── ic_more_vert_grey_18dp.png │ │ │ ├── ic_spinner_grey_24dp.png │ │ │ ├── ic_stat_logo_white.png │ │ │ ├── ic_thumb_down_white_24dp.png │ │ │ └── ic_thumb_up_white_24dp.png │ │ │ ├── drawable-ldrtl-hdpi │ │ │ └── ic_arrow_back_black_24dp.png │ │ │ ├── drawable-ldrtl-mdpi │ │ │ └── ic_arrow_back_black_24dp.png │ │ │ ├── drawable-ldrtl-xhdpi │ │ │ └── ic_arrow_back_black_24dp.png │ │ │ ├── drawable-ldrtl-xxhdpi │ │ │ └── ic_arrow_back_black_24dp.png │ │ │ ├── drawable-mdpi-v11 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-mdpi-v9 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-mdpi │ │ │ ├── ic_add_white_24dp.png │ │ │ ├── ic_arrow_back_black_24dp.png │ │ │ ├── ic_av_album_70dp.png │ │ │ ├── ic_check_box_orange_24dp.png │ │ │ ├── ic_check_box_outline_orange_24dp.png │ │ │ ├── ic_file_download_white_24dp.png │ │ │ ├── ic_logo_24dp.png │ │ │ ├── ic_logo_no_bg_100dp.png │ │ │ ├── ic_more_vert_grey_18dp.png │ │ │ ├── ic_spinner_grey_24dp.png │ │ │ ├── ic_stat_logo_white.png │ │ │ ├── ic_thumb_down_white_24dp.png │ │ │ └── ic_thumb_up_white_24dp.png │ │ │ ├── drawable-xhdpi-v11 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-xhdpi-v9 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-xhdpi │ │ │ ├── ic_add_white_24dp.png │ │ │ ├── ic_arrow_back_black_24dp.png │ │ │ ├── ic_av_album_70dp.png │ │ │ ├── ic_check_box_orange_24dp.png │ │ │ ├── ic_check_box_outline_orange_24dp.png │ │ │ ├── ic_file_download_white_24dp.png │ │ │ ├── ic_logo_24dp.png │ │ │ ├── ic_logo_no_bg_100dp.png │ │ │ ├── ic_more_vert_grey_18dp.png │ │ │ ├── ic_spinner_grey_24dp.png │ │ │ ├── ic_stat_logo_white.png │ │ │ ├── ic_thumb_down_white_24dp.png │ │ │ └── ic_thumb_up_white_24dp.png │ │ │ ├── drawable-xxhdpi-v11 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-xxhdpi-v9 │ │ │ └── ic_stat_logo_white.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── ic_add_white_24dp.png │ │ │ ├── ic_arrow_back_black_24dp.png │ │ │ ├── ic_av_album_70dp.png │ │ │ ├── ic_check_box_orange_24dp.png │ │ │ ├── ic_check_box_outline_orange_24dp.png │ │ │ ├── ic_file_download_white_24dp.png │ │ │ ├── ic_logo_24dp.png │ │ │ ├── ic_logo_no_bg_100dp.png │ │ │ ├── ic_more_vert_grey_18dp.png │ │ │ ├── ic_spinner_grey_24dp.png │ │ │ ├── ic_stat_logo_white.png │ │ │ ├── ic_thumb_down_white_24dp.png │ │ │ └── ic_thumb_up_white_24dp.png │ │ │ ├── drawable │ │ │ ├── logo_tinypng.png │ │ │ └── simple_selector.xml │ │ │ ├── layout │ │ │ ├── activity_downloader.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_playlist_download.xml │ │ │ ├── activity_playlist_tracks.xml │ │ │ ├── activity_settings.xml │ │ │ ├── activity_settings_2.xml │ │ │ ├── activity_splash.xml │ │ │ ├── activity_test.xml │ │ │ ├── content_playlist_download.xml │ │ │ ├── content_settings.xml │ │ │ ├── content_splash.xml │ │ │ ├── fragment_playlists.xml │ │ │ ├── fragment_tracks.xml │ │ │ ├── input_dialog_layout.xml │ │ │ ├── its_row.xml │ │ │ └── playlist_row.xml │ │ │ ├── menu │ │ │ ├── menu_main.xml │ │ │ ├── menu_playlist_downloaded.xml │ │ │ ├── menu_playlist_not_downloaded.xml │ │ │ └── menu_playlist_tracks.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ └── styles.xml │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── ids.xml │ │ │ ├── plurals.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── provider_paths.xml │ │ │ └── settings_screen.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── theah64 │ │ └── musicdog │ │ └── ExampleUnitTest.java ├── build.gradle ├── extras │ ├── graphics │ │ ├── logo_original.png │ │ ├── logo_skelton_white.png │ │ └── logo_tinypng.png │ ├── key.jks │ ├── p_d_5.sh │ ├── ps_m.sh │ ├── pull_db.sh │ ├── scd.db │ └── screenshots │ │ ├── s1.png │ │ ├── s2.png │ │ ├── s3.png │ │ ├── s4.png │ │ └── s5.png ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lab │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── example │ │ ├── Lab.java │ │ └── MyClass.java ├── scd.db └── settings.gradle ├── sample_data.db ├── scd.db └── youtube.png /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | 3 | *.class 4 | 5 | # Mobile Tools for Java (J2ME) 6 | .mtj.tmp/ 7 | 8 | # Package Files # 9 | *.jar 10 | *.war 11 | *.ear 12 | 13 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 14 | hs_err_pid* 15 | 16 | app-debug.apk 17 | api/web/META-INF/context.xml 18 | 19 | 20 | */.idea/workspace.xml 21 | */.idea/tasks.xml 22 | */.idea/dataSources.local.xml 23 | */.idea/dataSources.local.ids 24 | */.idea/dataSources.xml 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SoundCloud Downloader 2 | An android application to download tracks/playlists from SoundCloud. 3 | 4 | ### As on 31 Aug 2018 5 | ``` 6 | 7200+ users, 1,38,000+ tracks downloaded 7 | ``` 8 | 9 | ### Demo 10 | 11 | [![IMAGE](https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/master/youtube.png)](https://www.youtube.com/watch?v=qv0OWufJOoU) 12 | 13 | ### Download 14 | 15 | You can download the latest stable APK from [here](https://github.com/theapache64/SoundCloud-Downloader/releases) 16 | 17 | #### TODOs 18 | 19 | - Nothing for now. 20 | - Algorithm walk through. 21 | 22 | #### Sad part :( 23 | 24 | - 2 times suspended and 1 time rejected from Google PlayStore. 25 | 26 | #### Statistics Queries 27 | 28 | - Installation per date 29 | 30 | ```sql 31 | 32 | SELECT 33 | DATE(u.created_at) AS date, 34 | COUNT(u.id) AS total_users_joined 35 | FROM 36 | users u 37 | GROUP BY 38 | DATE(u.created_at) 39 | ORDER BY 40 | date DESC; 41 | ``` 42 | 43 | - User stats 44 | 45 | ```sql 46 | SELECT 47 | u.id, 48 | u.name, 49 | u.email, 50 | u.imei, 51 | COUNT(DISTINCT r.id) AS total_requests, 52 | COUNT(DISTINCT dr.id) AS total_downloads, 53 | COUNT(DISTINCT t.id) AS total_tracks, 54 | u.is_active, 55 | ( 56 | SELECT 57 | soundcloud_url 58 | FROM 59 | requests 60 | WHERE 61 | user_id = u.id 62 | ORDER BY 63 | id DESC 64 | LIMIT 1 65 | ) AS last_hit 66 | FROM 67 | users u 68 | LEFT JOIN 69 | requests r ON r.user_id = u.id 70 | LEFT JOIN 71 | download_requests dr ON dr.request_id = r.id 72 | LEFT JOIN 73 | tracks t ON t.request_id = r.id 74 | GROUP BY 75 | u.id 76 | ORDER BY 77 | total_requests DESC; 78 | 79 | ``` 80 | 81 | #### Bugs? 82 | 83 | - Found one? shoot a mail to theapache64@gmail.com or create a repo issue. 84 | 85 | -------------------------------------------------------------------------------- /api/.idea/.name: -------------------------------------------------------------------------------- 1 | soundcloud_downloader -------------------------------------------------------------------------------- /api/.idea/artifacts/scd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/scd 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /api/.idea/artifacts/soundcloud_downloader_war_exploded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/soundcloud_downloader_war_exploded 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /api/.idea/dictionaries/theapache64.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | soundcloud 5 | 6 | 7 | -------------------------------------------------------------------------------- /api/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /api/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /api/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /api/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /api/soundcloud_downloader.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/Lab.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd; 2 | 3 | 4 | /** 5 | * Created by theapache64 on 8/12/16. 6 | */ 7 | public class Lab { 8 | public static void main(String[] args) { 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/database/Connection.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.database; 2 | 3 | 4 | import javax.naming.Context; 5 | import javax.naming.InitialContext; 6 | import javax.naming.NamingException; 7 | import javax.sql.DataSource; 8 | import java.sql.SQLException; 9 | 10 | /** 11 | * Created by theapache64 on 10/13/2015. 12 | */ 13 | public class Connection { 14 | 15 | private static final boolean debugMode = false; 16 | private static DataSource ds; 17 | 18 | public static java.sql.Connection getConnection() { 19 | 20 | try { 21 | 22 | if (ds == null) { 23 | final Context initContext = new InitialContext(); 24 | Context envContext = (Context) initContext.lookup("java:/comp/env"); 25 | ds = (DataSource) envContext.lookup("jdbc/scd"); 26 | } 27 | 28 | return ds.getConnection(); 29 | 30 | } catch (NamingException | SQLException e) { 31 | e.printStackTrace(); 32 | throw new IllegalArgumentException("Connection error : " + e.getMessage()); 33 | } 34 | } 35 | 36 | public static boolean isDebugMode() { 37 | return debugMode; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/database/tables/DownloadRequests.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.database.tables; 2 | 3 | import com.theah64.scd.database.Connection; 4 | import com.theah64.scd.models.DownloadRequest; 5 | 6 | import java.sql.PreparedStatement; 7 | import java.sql.SQLException; 8 | 9 | /** 10 | * Created by theapache64 on 26/12/16. 11 | */ 12 | public class DownloadRequests extends BaseTable { 13 | public static final String COLUMN_REQUEST_ID = "request_id"; 14 | private static DownloadRequests instance; 15 | 16 | private DownloadRequests() { 17 | super("download_requests"); 18 | } 19 | 20 | public static DownloadRequests getInstance() { 21 | if (instance == null) { 22 | instance = new DownloadRequests(); 23 | } 24 | return instance; 25 | } 26 | 27 | @Override 28 | public boolean add(DownloadRequest request) throws InsertFailedException { 29 | boolean isAdded = false; 30 | final String query = "INSERT INTO download_requests (track_id, request_id,client_id, download_url) VALUES (?,?,?,?);"; 31 | final java.sql.Connection con = Connection.getConnection(); 32 | try { 33 | final PreparedStatement ps = con.prepareStatement(query); 34 | ps.setString(1, request.getTrackId()); 35 | ps.setString(2, request.getRequestId()); 36 | ps.setString(3, request.getClientId()); 37 | ps.setString(4, request.getDownloadLink()); 38 | isAdded = ps.executeUpdate() == 1; 39 | ps.close(); 40 | } catch (SQLException e) { 41 | e.printStackTrace(); 42 | } finally { 43 | try { 44 | con.close(); 45 | } catch (SQLException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | if (!isAdded) { 50 | throw new InsertFailedException("Failed to add new download request"); 51 | } 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/database/tables/Preference.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.database.tables; 2 | 3 | 4 | import com.theah64.scd.database.Connection; 5 | 6 | import java.sql.PreparedStatement; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | 10 | /** 11 | * Created by theapache64 on 27/8/16. 12 | */ 13 | public class Preference extends BaseTable { 14 | 15 | private static final String COLUMN_KEY = "_key"; 16 | private static final String COLUMN_VALUE = "_value"; 17 | public static final String KEY_GMAIL_USERNAME = "gmail_username"; 18 | public static final String KEY_GMAIL_PASSWORD = "gmail_password"; 19 | public static final String KEY_ADMIN_EMAIL = "admin_email"; 20 | public static final String KEY_FILENAME_FORMAT = "filename_format"; 21 | public static final String KEY_IS_DEBUG_DOWNLOAD = "is_debug_download"; 22 | public static final String KEY_APK_URL = "apk_url"; 23 | private static Preference instance = new Preference(); 24 | 25 | private Preference() { 26 | super("preferences"); 27 | } 28 | 29 | public static Preference getInstance() { 30 | return instance; 31 | } 32 | 33 | 34 | @Override 35 | public String get(String column, String key) { 36 | String value = null; 37 | final String query = String.format("SELECT _value FROM preference WHERE %s = ? LIMIT 1", column); 38 | final java.sql.Connection con = Connection.getConnection(); 39 | try { 40 | final PreparedStatement ps = con.prepareStatement(query); 41 | ps.setString(1, key); 42 | 43 | final ResultSet rs = ps.executeQuery(); 44 | if (rs.first()) { 45 | value = rs.getString(COLUMN_VALUE); 46 | } 47 | 48 | ps.close(); 49 | 50 | } catch (SQLException e) { 51 | e.printStackTrace(); 52 | } finally { 53 | try { 54 | con.close(); 55 | } catch (SQLException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | return value; 60 | } 61 | 62 | 63 | public String getString(final String key) { 64 | return get(COLUMN_KEY, key); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/database/tables/Requests.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.database.tables; 2 | 3 | 4 | import com.theah64.scd.database.Connection; 5 | import com.theah64.scd.models.Request; 6 | 7 | import javax.xml.transform.Result; 8 | import java.sql.PreparedStatement; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | 12 | /** 13 | * Created by theapache64 on 2/12/16. 14 | */ 15 | public class Requests extends BaseTable { 16 | 17 | private static final Requests instance = new Requests(); 18 | public static final String COLUMN_SOUND_CLOUD_URL = "soundcloud_url"; 19 | 20 | private Requests() { 21 | super("requests"); 22 | } 23 | 24 | public static Requests getInstance() { 25 | return instance; 26 | } 27 | 28 | @Override 29 | public String addv3(Request request) throws InsertFailedException { 30 | String requestId = null; 31 | 32 | final String query = "INSERT INTO requests (user_id, soundcloud_url) VALUES (?,?);"; 33 | final java.sql.Connection con = Connection.getConnection(); 34 | try { 35 | final PreparedStatement ps = con.prepareStatement(query, PreparedStatement.RETURN_GENERATED_KEYS); 36 | 37 | ps.setString(1, request.getUserId()); 38 | ps.setString(2, request.getSoundCloudUrl()); 39 | ps.executeUpdate(); 40 | 41 | final ResultSet rs = ps.getGeneratedKeys(); 42 | 43 | if (rs.first()) { 44 | requestId = rs.getString(1); 45 | } 46 | 47 | rs.close(); 48 | ps.close(); 49 | 50 | } catch (SQLException e) { 51 | e.printStackTrace(); 52 | } finally { 53 | try { 54 | con.close(); 55 | } catch (SQLException e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | if (requestId == null) { 61 | throw new InsertFailedException("Failed to add request"); 62 | } 63 | 64 | System.out.println("Created new request: " + requestId); 65 | 66 | return requestId; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/database/tables/SCClients.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.database.tables; 2 | 3 | import com.sun.istack.internal.NotNull; 4 | import com.theah64.scd.models.SCClient; 5 | 6 | import java.sql.Connection; 7 | import java.sql.ResultSet; 8 | import java.sql.SQLException; 9 | import java.sql.Statement; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by theapache64 on 29/12/16. 15 | */ 16 | public class SCClients extends BaseTable { 17 | 18 | private static final String COLUMN_CLIENT_ID = "client_id"; 19 | private static final String COLUMN_AS_TOTAL_HITS = "total_hits"; 20 | private static SCClients instance; 21 | 22 | private SCClients() { 23 | super("sc_clients"); 24 | } 25 | 26 | public static SCClients getInstance() { 27 | if (instance == null) { 28 | instance = new SCClients(); 29 | } 30 | return instance; 31 | } 32 | 33 | @NotNull 34 | public List getAll() { 35 | List clients = null; 36 | final String query = "SELECT s.id,s.name,s.client_id,s.is_active, ((SELECT COUNT(t.id) FROM tracks t WHERE t.client_id = s.id) + (SELECT COUNT(dr.id) FROM download_requests dr WHERE dr.client_id = s.id)) AS total_hits FROM sc_clients s;"; 37 | 38 | final Connection con = com.theah64.scd.database.Connection.getConnection(); 39 | try { 40 | final Statement stmt = con.createStatement(); 41 | final ResultSet rs = stmt.executeQuery(query); 42 | 43 | if (rs.first()) { 44 | 45 | clients = new ArrayList<>(); 46 | 47 | do { 48 | 49 | final String id = rs.getString(COLUMN_ID); 50 | final String name = rs.getString(COLUMN_NAME); 51 | final String clientId = rs.getString(COLUMN_CLIENT_ID); 52 | final boolean isActive = rs.getBoolean(COLUMN_IS_ACTIVE); 53 | final int totalHits = rs.getInt(COLUMN_AS_TOTAL_HITS); 54 | 55 | clients.add(new SCClient(id, name, clientId, totalHits, isActive)); 56 | 57 | } while (rs.next()); 58 | } 59 | 60 | rs.close(); 61 | stmt.close(); 62 | 63 | } catch (SQLException e) { 64 | e.printStackTrace(); 65 | } finally { 66 | try { 67 | con.close(); 68 | } catch (SQLException e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | if (clients == null) { 74 | throw new IllegalArgumentException("Clients can't be null"); 75 | } 76 | return clients; 77 | } 78 | 79 | public SCClient getLeastUsedClient() { 80 | 81 | System.out.println("Getting least-used-sc-client"); 82 | 83 | SCClient client = null; 84 | final String query = "SELECT s.id,s.name,s.client_id, ((SELECT COUNT(t.id) FROM tracks t WHERE t.client_id = s.id) + (SELECT COUNT(dr.id) FROM download_requests dr WHERE dr.client_id = s.id)) AS total_hits FROM sc_clients s WHERE is_active =1 ORDER BY total_hits LIMIT 1;"; 85 | 86 | final Connection con = com.theah64.scd.database.Connection.getConnection(); 87 | try { 88 | final Statement stmt = con.createStatement(); 89 | final ResultSet rs = stmt.executeQuery(query); 90 | 91 | if (rs.first()) { 92 | final String id = rs.getString(COLUMN_ID); 93 | final String name = rs.getString(COLUMN_NAME); 94 | final String clientId = rs.getString(COLUMN_CLIENT_ID); 95 | final int totalHits = rs.getInt(COLUMN_AS_TOTAL_HITS); 96 | 97 | client = new SCClient(id, name, clientId, totalHits, true); 98 | 99 | System.out.println("Found least-used-sc-client : " + client); 100 | } 101 | 102 | rs.close(); 103 | stmt.close(); 104 | 105 | } catch (SQLException e) { 106 | e.printStackTrace(); 107 | } finally { 108 | try { 109 | con.close(); 110 | } catch (SQLException e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | 115 | if (client == null) { 116 | throw new IllegalArgumentException("No soundcloud client found"); 117 | } 118 | 119 | return client; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/DownloadRequest.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | /** 4 | * Created by theapache64 on 26/12/16. 5 | */ 6 | public class DownloadRequest { 7 | private final String id, trackId, requestId, clientId, downloadLink; 8 | 9 | public DownloadRequest(String id, String trackId, String requestId, String clientId, String downloadLink) { 10 | this.id = id; 11 | this.trackId = trackId; 12 | this.requestId = requestId; 13 | this.clientId = clientId; 14 | this.downloadLink = downloadLink; 15 | } 16 | 17 | public String getRequestId() { 18 | return requestId; 19 | } 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public String getTrackId() { 26 | return trackId; 27 | } 28 | 29 | public String getDownloadLink() { 30 | return downloadLink; 31 | } 32 | 33 | public String getClientId() { 34 | return clientId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/JSONTracks.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | import org.json.JSONArray; 4 | 5 | /** 6 | * Created by theapache64 on 10/12/16. 7 | */ 8 | public class JSONTracks { 9 | 10 | public static final String KEY_TRACKS = "tracks"; 11 | 12 | private final String playlistName, username, artworkUrl; 13 | private final JSONArray jaTracks; 14 | private final String requestId; 15 | 16 | public JSONTracks(String playlistName, String username, String artworkUrl, JSONArray jaTracks, String requestId) { 17 | this.playlistName = playlistName; 18 | this.username = username; 19 | this.artworkUrl = artworkUrl; 20 | this.jaTracks = jaTracks; 21 | this.requestId = requestId; 22 | } 23 | 24 | public String getPlaylistName() { 25 | return playlistName; 26 | } 27 | 28 | public String getArtworkUrl() { 29 | return artworkUrl; 30 | } 31 | 32 | public JSONArray getJSONArrayTracks() { 33 | return jaTracks; 34 | } 35 | 36 | public String getUsername() { 37 | return username; 38 | } 39 | 40 | public String getRequestId() { 41 | return requestId; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/Request.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | /** 4 | * Created by theapache64 on 2/12/16. 5 | */ 6 | public class Request { 7 | private final String userId, soundCloudUrl; 8 | 9 | public Request(String userId, String soundCloudUrl) { 10 | this.userId = userId; 11 | this.soundCloudUrl = soundCloudUrl; 12 | } 13 | 14 | public String getUserId() { 15 | return userId; 16 | } 17 | 18 | public String getSoundCloudUrl() { 19 | return soundCloudUrl; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/SCClient.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | /** 4 | * Created by theapache64 on 29/12/16. 5 | */ 6 | public class SCClient { 7 | private final String id, name, clientId; 8 | private final int totalHits; 9 | private final boolean isActive; 10 | 11 | public SCClient(String id, String name, String clientId, int totalHits, boolean isActive) { 12 | this.id = id; 13 | this.name = name; 14 | this.clientId = clientId; 15 | this.totalHits = totalHits; 16 | this.isActive = isActive; 17 | } 18 | 19 | public String getId() { 20 | return id; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public String getClientId() { 28 | return clientId; 29 | } 30 | 31 | public int getTotalHits() { 32 | return totalHits; 33 | } 34 | 35 | public boolean isActive() { 36 | return isActive; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "SCClient{" + 42 | "id='" + id + '\'' + 43 | ", name='" + name + '\'' + 44 | ", clientId='" + clientId + '\'' + 45 | ", totalHits=" + totalHits + 46 | '}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/Track.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | import com.theah64.scd.database.tables.Tracks; 4 | import org.json.JSONException; 5 | import org.json.JSONObject; 6 | 7 | /** 8 | * Created by theapache64 on 8/12/16. 9 | */ 10 | public class Track { 11 | 12 | 13 | public static final String KEY_PLAYLIST_NAME = "playlist_name"; 14 | private static final String KEY_DOWNLOAD_URL = "download_url"; 15 | 16 | private String id; 17 | private final String primaryRequestId; 18 | private final String soundcloudUrl; 19 | private final String soundcloudTrackId; 20 | private final String title; 21 | private final String username; 22 | private final String artworkUrl; 23 | private final String filename; 24 | private final String originalFormat; 25 | private final long duration; 26 | private final String clientId; 27 | 28 | public Track(String id, String primaryRequestId, String soundcloudUrl, String soundcloudTrackId, String title, String username, String artworkUrl, String filename, String originalFormat, long duration, String clientId) { 29 | this.id = id; 30 | this.primaryRequestId = primaryRequestId; 31 | this.soundcloudUrl = soundcloudUrl; 32 | this.soundcloudTrackId = soundcloudTrackId; 33 | this.title = title; 34 | this.username = username; 35 | this.artworkUrl = artworkUrl; 36 | this.filename = filename; 37 | this.originalFormat = originalFormat; 38 | this.duration = duration; 39 | this.clientId = clientId; 40 | } 41 | 42 | public String getId() { 43 | return id; 44 | } 45 | 46 | public String getPrimaryRequestId() { 47 | return primaryRequestId; 48 | } 49 | 50 | public String getSoundcloudUrl() { 51 | return soundcloudUrl; 52 | } 53 | 54 | public String getSoundcloudTrackId() { 55 | return soundcloudTrackId; 56 | } 57 | 58 | public String getTitle() { 59 | return title; 60 | } 61 | 62 | public String getUsername() { 63 | return username; 64 | } 65 | 66 | public String getArtworkUrl() { 67 | return artworkUrl; 68 | } 69 | 70 | public String getFilename() { 71 | return filename; 72 | } 73 | 74 | public String getOriginalFormat() { 75 | return originalFormat; 76 | } 77 | 78 | public long getDuration() { 79 | return duration; 80 | } 81 | 82 | public String getClientId() { 83 | return clientId; 84 | } 85 | 86 | public JSONObject toJSONObject() throws JSONException { 87 | final JSONObject joTrack = new JSONObject(); 88 | joTrack.put(Tracks.COLUMN_ID, id); 89 | joTrack.put(Tracks.COLUMN_TITLE, title); 90 | joTrack.put(Tracks.COLUMN_ORIGINAL_FORMAT, originalFormat); 91 | joTrack.put(Tracks.COLUMN_FILENAME, filename); 92 | joTrack.put(Tracks.COLUMN_ARTWORK_URL, artworkUrl); 93 | joTrack.put(Tracks.COLUMN_DURATION, duration); 94 | joTrack.put(Tracks.COLUMN_USERNAME, username); 95 | joTrack.put(Tracks.COLUMN_SOUNDCLOUD_URL, soundcloudUrl); 96 | return joTrack; 97 | } 98 | 99 | 100 | public void setId(String id) { 101 | this.id = id; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/models/User.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.models; 2 | 3 | /** 4 | * Created by theapache64 on 15/10/16. 5 | */ 6 | public class User { 7 | 8 | private final String id, name, email, imei, apiKey, deviceHash, lastHit; 9 | private final boolean isActive; 10 | private final long totalRequests, totalDownloads, totalTracks; 11 | 12 | public User(String id, String name, String email, String imei, String apiKey, String deviceHash, String lastHit, boolean isActive, long totalRequests, long totalDownloads, long totalTracks) { 13 | this.id = id; 14 | this.name = name; 15 | this.email = email; 16 | this.imei = imei; 17 | this.apiKey = apiKey; 18 | this.deviceHash = deviceHash; 19 | this.lastHit = lastHit; 20 | this.totalRequests = totalRequests; 21 | this.isActive = isActive; 22 | this.totalDownloads = totalDownloads; 23 | this.totalTracks = totalTracks; 24 | } 25 | 26 | public String getLastHit() { 27 | return lastHit; 28 | } 29 | 30 | public long getTotalDownloads() { 31 | return totalDownloads; 32 | } 33 | 34 | public long getTotalTracks() { 35 | return totalTracks; 36 | } 37 | 38 | public String getEmail() { 39 | return email; 40 | } 41 | 42 | public String getId() { 43 | return id; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public String getApiKey() { 51 | return apiKey; 52 | } 53 | 54 | public String getDeviceHash() { 55 | return deviceHash; 56 | } 57 | 58 | public boolean isActive() { 59 | return isActive; 60 | } 61 | 62 | public String getIMEI() { 63 | return imei; 64 | } 65 | 66 | public long getTotalRequests() { 67 | return totalRequests; 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | return "User{" + 73 | "id='" + id + '\'' + 74 | ", name='" + name + '\'' + 75 | ", email='" + email + '\'' + 76 | ", imei='" + imei + '\'' + 77 | ", apiKey='" + apiKey + '\'' + 78 | ", deviceHash='" + deviceHash + '\'' + 79 | ", lastHit='" + lastHit + '\'' + 80 | ", isActive=" + isActive + 81 | ", totalRequests=" + totalRequests + 82 | ", totalDownloads=" + totalDownloads + 83 | ", totalTracks=" + totalTracks + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/servlets/DownloadAPKServlet.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.servlets; 2 | 3 | import com.theah64.scd.database.tables.Preference; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.annotation.WebServlet; 7 | import javax.servlet.http.HttpServlet; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | 12 | /** 13 | * Created by theapache64 on 23/12/16. 14 | */ 15 | @WebServlet(urlPatterns = {AdvancedBaseServlet.VERSION_CODE + "/soundclouddownloader.apk"}) 16 | public class DownloadAPKServlet extends HttpServlet { 17 | 18 | @Override 19 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 20 | resp.sendRedirect(Preference.getInstance().getString(Preference.KEY_APK_URL)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/servlets/INServlet.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.servlets; 2 | 3 | 4 | import com.theah64.scd.database.tables.BaseTable; 5 | import com.theah64.scd.database.tables.Preference; 6 | import com.theah64.scd.database.tables.Users; 7 | import com.theah64.scd.models.User; 8 | import com.theah64.scd.utils.APIResponse; 9 | import com.theah64.scd.utils.MailHelper; 10 | import com.theah64.scd.utils.Request; 11 | import org.json.JSONException; 12 | 13 | import javax.servlet.annotation.WebServlet; 14 | import java.io.IOException; 15 | import java.io.PrintWriter; 16 | import java.util.Random; 17 | 18 | /** 19 | * Created by Shifar Shifz on 10/18/2015. 20 | */ 21 | @WebServlet(urlPatterns = {AdvancedBaseServlet.VERSION_CODE + "/in"}) 22 | public class INServlet extends AdvancedBaseServlet { 23 | 24 | //Used to generate a new user 25 | private static final int API_KEY_LENGTH = 10; 26 | private static final String apiEngine = "0123456789AaBbCcDdEeFfGgHhIiJjKkLkMmNnOoPpQqRrSsTtUuVvWwXxYyZ"; 27 | private static Random random; 28 | 29 | private static String getNewApiKey() { 30 | if (random == null) { 31 | random = new Random(); 32 | } 33 | final StringBuilder apiKeyBuilder = new StringBuilder(); 34 | for (int i = 0; i < API_KEY_LENGTH; i++) { 35 | apiKeyBuilder.append(apiEngine.charAt(random.nextInt(apiEngine.length()))); 36 | } 37 | return apiKeyBuilder.toString(); 38 | } 39 | 40 | 41 | @Override 42 | public boolean isBinaryServlet() { 43 | return false; 44 | } 45 | 46 | @Override 47 | protected boolean isSecureServlet() { 48 | return false; 49 | } 50 | 51 | @Override 52 | protected String[] getRequiredParameters() { 53 | return new String[]{Users.COLUMN_DEVICE_HASH, Users.COLUMN_IMEI}; 54 | } 55 | 56 | @Override 57 | protected void doAdvancedPost() throws BaseTable.InsertFailedException, JSONException, BaseTable.UpdateFailedException, Request.RequestException, IOException { 58 | 59 | final PrintWriter out = getWriter(); 60 | 61 | final String deviceHash = getStringParameter(Users.COLUMN_DEVICE_HASH); 62 | 63 | //Checking if any account exist with the requested imei 64 | final Users users = Users.getInstance(); 65 | User user = users.get(Users.COLUMN_DEVICE_HASH, deviceHash); 66 | 67 | if (user != null && !user.isActive()) { 68 | throw new Request.RequestException("You're banned from using SoundCloud Downloader API"); 69 | } 70 | 71 | final boolean isAlreadyExist = user != null; 72 | 73 | if (!isAlreadyExist) { 74 | 75 | //Account not exists, so creating new one 76 | final String name = getStringParameter(Users.COLUMN_NAME); 77 | final String imei = getStringParameter(Users.COLUMN_IMEI); 78 | final String email = getStringParameter(Users.COLUMN_EMAIL); 79 | user = new User(name, name, email, imei, getNewApiKey(), deviceHash, null, true, 0, 0, 0); 80 | users.add(user); 81 | 82 | final String userString = user.toString(); 83 | 84 | new Thread(new Runnable() { 85 | @Override 86 | public void run() { 87 | 88 | final String adminEmail = Preference.getInstance().getString(Preference.KEY_ADMIN_EMAIL); 89 | MailHelper.sendMail(adminEmail, "User: " + userString); 90 | 91 | } 92 | }).start(); 93 | } 94 | 95 | final String message = isAlreadyExist ? "Welcome back!" : "Welcome!"; 96 | 97 | //At this point, we've a user - no matter new or old 98 | out.write(new APIResponse(message, Users.COLUMN_API_KEY, user.getApiKey()).getResponse()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/servlets/TracksServlet.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.servlets; 2 | 3 | import com.theah64.scd.core.SoundCloudDownloader; 4 | import com.theah64.scd.database.tables.BaseTable; 5 | import com.theah64.scd.database.tables.Preference; 6 | import com.theah64.scd.database.tables.Requests; 7 | import com.theah64.scd.database.tables.Tracks; 8 | import com.theah64.scd.models.JSONTracks; 9 | import com.theah64.scd.models.Track; 10 | import com.theah64.scd.utils.APIResponse; 11 | import com.theah64.scd.utils.Request; 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import javax.servlet.ServletException; 16 | import javax.servlet.annotation.WebServlet; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | /** 22 | * Created by theapache64 on 8/12/16. 23 | */ 24 | @WebServlet(urlPatterns = {AdvancedBaseServlet.VERSION_CODE + "/json"}) 25 | public final class TracksServlet extends AdvancedBaseServlet { 26 | 27 | private static final String KEY_TRACKS = "tracks"; 28 | 29 | @Override 30 | public boolean isBinaryServlet() { 31 | return false; 32 | } 33 | 34 | @Override 35 | protected boolean isSecureServlet() { 36 | return true; 37 | } 38 | 39 | @Override 40 | protected String[] getRequiredParameters() { 41 | return new String[]{Requests.COLUMN_SOUND_CLOUD_URL}; 42 | } 43 | 44 | @Override 45 | protected void doAdvancedPost() throws BaseTable.InsertFailedException, JSONException, BaseTable.UpdateFailedException, Request.RequestException, IOException { 46 | 47 | final JSONTracks jTracks = getTracks(); 48 | 49 | if (jTracks != null) { 50 | 51 | final JSONObject joTrack = new JSONObject(); 52 | 53 | if (jTracks.getPlaylistName() != null) { 54 | joTrack.put(Track.KEY_PLAYLIST_NAME, jTracks.getPlaylistName()); 55 | joTrack.put(Tracks.COLUMN_USERNAME, jTracks.getUsername()); 56 | 57 | //Playlist cover 58 | joTrack.put(Tracks.COLUMN_ARTWORK_URL, jTracks.getArtworkUrl()); 59 | } 60 | 61 | 62 | joTrack.put(KEY_TRACKS, jTracks.getJSONArrayTracks()); 63 | joTrack.put(Tracks.COLUMN_REQUEST_ID, jTracks.getRequestId()); 64 | 65 | getWriter().write(new APIResponse("Request processed", joTrack).getResponse()); 66 | } else { 67 | getWriter().write(new APIResponse("Track unavailable to download.Please try another one.").getResponse()); 68 | } 69 | } 70 | 71 | private JSONTracks getTracks() throws BaseTable.InsertFailedException, JSONException { 72 | String soundCloudUrl = getStringParameter(Tracks.COLUMN_SOUNDCLOUD_URL).replaceAll("^https", "http"); 73 | final com.theah64.scd.models.Request apiRequest = new com.theah64.scd.models.Request(getHeaderSecurity().getUserId(), soundCloudUrl); 74 | final String requestId = Requests.getInstance().addv3(apiRequest); 75 | return SoundCloudDownloader.getSoundCloudTracks(requestId, soundCloudUrl); 76 | } 77 | } -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/APIResponse.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * Created by theapache64 on 11/9/16. 8 | */ 9 | public class APIResponse { 10 | 11 | private static final int ERROR_CODE_NO_ERROR = 0; 12 | private static final String KEY_ERROR = "error"; 13 | private static final String KEY_ERROR_CODE = "error_code"; 14 | private static final String KEY_MESSAGE = "message"; 15 | private static final String KEY_DATA = "data"; 16 | private final JSONObject joResp = new JSONObject(); 17 | 18 | private final boolean hasError; 19 | private final int errorCode; 20 | private final String message; 21 | private final JSONObject joData; 22 | 23 | 24 | private APIResponse(final boolean hasError, final int errorCode, final String message, final JSONObject joData) { 25 | this.hasError = hasError; 26 | this.errorCode = errorCode; 27 | this.message = message; 28 | this.joData = joData; 29 | } 30 | 31 | public APIResponse(final String message, final JSONObject joData) { 32 | this(false, ERROR_CODE_NO_ERROR, message, joData); 33 | } 34 | 35 | public APIResponse(final String message, final String key, final String value) throws JSONException { 36 | this(false, ERROR_CODE_NO_ERROR, message, new JSONObject().put(key, value)); 37 | } 38 | 39 | public APIResponse(final int errorCode, final String errorMessage) { 40 | this(true, errorCode, errorMessage, null); 41 | } 42 | 43 | public APIResponse(final String errorMessage) { 44 | this(true, 1, errorMessage, null); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return getResponse(); 50 | } 51 | 52 | public String getResponse() { 53 | 54 | try { 55 | joResp.put(KEY_ERROR, hasError); 56 | joResp.put(KEY_ERROR_CODE, errorCode); 57 | joResp.put(KEY_MESSAGE, message); 58 | 59 | if (joData != null) { 60 | joResp.put(KEY_DATA, joData); 61 | } 62 | 63 | } catch (JSONException e) { 64 | e.printStackTrace(); 65 | } 66 | 67 | return joResp.toString(); 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/FileNameUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | /** 4 | * Created by theapache64 on 15/12/16. 5 | */ 6 | public class FileNameUtils { 7 | public static String getSanitizedName(final String fileName) { 8 | return fileName.replaceAll("[^\\w]", "_").replaceAll("[_]{2,}", "_"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/HeaderSecurity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | 4 | import com.theah64.scd.database.tables.Users; 5 | 6 | /** 7 | * Created by shifar on 31/12/15. 8 | */ 9 | public class HeaderSecurity { 10 | 11 | public static final String KEY_AUTHORIZATION = "Authorization"; 12 | private static final String REASON_API_KEY_MISSING = "API key is missing"; 13 | private static final String REASON_INVALID_API_KEY = "Invalid API key"; 14 | private final String authorization; 15 | private String userId; 16 | 17 | public HeaderSecurity(final String authorization) throws Request.RequestException { 18 | //Collecting header from passed request 19 | this.authorization = authorization; 20 | isAuthorized(); 21 | } 22 | 23 | /** 24 | * Used to identify if passed API-KEY has a valid victim. 25 | */ 26 | private void isAuthorized() throws Request.RequestException { 27 | 28 | if (this.authorization == null) { 29 | //No api key passed along with request 30 | throw new Request.RequestException("Unauthorized access"); 31 | } 32 | 33 | final Users users = Users.getInstance(); 34 | this.userId = users.get(Users.COLUMN_API_KEY, this.authorization, Users.COLUMN_ID, true); 35 | if (this.userId == null) { 36 | throw new Request.RequestException("No user found with the api_key " + this.authorization); 37 | } 38 | 39 | } 40 | 41 | public String getUserId() { 42 | return this.userId; 43 | } 44 | 45 | public String getFailureReason() { 46 | return this.authorization == null ? REASON_API_KEY_MISSING : REASON_INVALID_API_KEY; 47 | } 48 | 49 | public String getAuthorization() { 50 | return authorization; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/MailHelper.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | 4 | import com.theah64.scd.database.tables.Preference; 5 | 6 | import javax.mail.*; 7 | import javax.mail.internet.InternetAddress; 8 | import javax.mail.internet.MimeMessage; 9 | import java.util.Properties; 10 | 11 | /** 12 | * Created by shifar on 15/4/16. 13 | */ 14 | public class MailHelper { 15 | 16 | private static String gmailUsername, gmailPassword; 17 | 18 | public static boolean sendMail(String email, String message) { 19 | 20 | 21 | System.out.println("Sending email to " + email); 22 | 23 | if (gmailUsername == null || gmailPassword == null) { 24 | final Preference preference = Preference.getInstance(); 25 | 26 | gmailUsername = preference.getString(Preference.KEY_GMAIL_USERNAME); 27 | gmailPassword = preference.getString(Preference.KEY_GMAIL_PASSWORD); 28 | } 29 | 30 | System.out.println("u:" + gmailUsername); 31 | System.out.println("p:" + gmailPassword); 32 | 33 | 34 | final Properties properties = new Properties(); 35 | properties.put("mail.smtp.host", "smtp.gmail.com"); 36 | properties.put("mail.smtp.auth", "true"); 37 | properties.put("mail.smtp.port", "465"); 38 | properties.put("mail.smtp.socketFactory.port", "465"); 39 | properties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); 40 | 41 | Session session = Session.getInstance(properties, new Authenticator() { 42 | @Override 43 | protected PasswordAuthentication getPasswordAuthentication() { 44 | return new PasswordAuthentication(gmailUsername, gmailPassword); 45 | } 46 | }); 47 | 48 | Message mimeMessage = new MimeMessage(session); 49 | try { 50 | mimeMessage.setFrom(new InternetAddress(gmailUsername)); 51 | mimeMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email)); 52 | mimeMessage.setSubject("New user @ SCD"); 53 | mimeMessage.setText(message); 54 | 55 | Transport.send(mimeMessage); 56 | System.out.println("Mail sent :" + message); 57 | return true; 58 | } catch (MessagingException e) { 59 | e.printStackTrace(); 60 | } 61 | 62 | System.out.println("Failed to send mail"); 63 | 64 | return false; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | import com.sun.istack.internal.Nullable; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.net.URL; 10 | 11 | /** 12 | * Created by Shifar Shifz on 10/22/2015. 13 | */ 14 | public class NetworkHelper { 15 | 16 | private final String url; 17 | 18 | public NetworkHelper(final String url) { 19 | this.url = url; 20 | } 21 | 22 | @Nullable 23 | public String getResponse() { 24 | 25 | //System.out.println("->" + url); 26 | try { 27 | 28 | final URL urlOb = new URL(url); 29 | final InputStream is = urlOb.openStream(); 30 | final InputStreamReader isr = new InputStreamReader(is); 31 | final BufferedReader br = new BufferedReader(isr); 32 | final StringBuilder response = new StringBuilder(); 33 | String line; 34 | while ((line = br.readLine()) != null) { 35 | response.append(line).append("\n"); 36 | } 37 | is.close(); 38 | isr.close(); 39 | br.close(); 40 | 41 | return response.toString(); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | return null; 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /api/src/com/theah64/scd/utils/Request.java: -------------------------------------------------------------------------------- 1 | package com.theah64.scd.utils; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by theapache64 on 11/22/2015. 10 | */ 11 | public class Request { 12 | 13 | private final HttpServletRequest request; 14 | private final String[] requiredParams; 15 | private List missingOrInvalidParams; 16 | 17 | public Request(HttpServletRequest request, String[] requiredParams) throws RequestException { 18 | this.request = request; 19 | this.requiredParams = requiredParams; 20 | 21 | if (!hasAllParams()) { 22 | throw new RequestException(getErrorReport()); 23 | } 24 | } 25 | 26 | /** 27 | * True if all the requiredParam exist and !empty other wise false 28 | * 29 | * @return Boolean 30 | */ 31 | public boolean hasAllParams() { 32 | return has(requiredParams); 33 | } 34 | 35 | 36 | private String getErrorReport() { 37 | 38 | if (missingOrInvalidParams != null && !missingOrInvalidParams.isEmpty()) { 39 | final StringBuilder errorReportBuilder = new StringBuilder("Missing or empty value for "); 40 | for (final String param : missingOrInvalidParams) { 41 | errorReportBuilder.append(param).append(","); 42 | } 43 | //Removing last comma and returns the error report. 44 | return errorReportBuilder.substring(0, errorReportBuilder.length() - 1); 45 | } 46 | 47 | return null; 48 | } 49 | 50 | public String getStringParameter(String key) { 51 | return this.request.getParameter(key); 52 | } 53 | 54 | public boolean getBooleanParameter(String key) { 55 | return Boolean.parseBoolean(getStringParameter(key)); 56 | } 57 | 58 | public boolean has(final String... requiredParamKeys) { 59 | 60 | Map paramMap = request.getParameterMap(); 61 | 62 | if (this.missingOrInvalidParams != null && !this.missingOrInvalidParams.isEmpty()) { 63 | //Clears old history if exist. 64 | this.missingOrInvalidParams.clear(); 65 | } 66 | 67 | for (final String reqParam : requiredParamKeys) { 68 | 69 | final String[] reqParamValue = paramMap.get(reqParam); 70 | 71 | if (reqParamValue == null || reqParamValue[0].trim().isEmpty()) { 72 | 73 | //Lazy init 74 | if (this.missingOrInvalidParams == null) { 75 | this.missingOrInvalidParams = new ArrayList<>(); 76 | } 77 | 78 | //Invalid or missing Adding bad param name to list 79 | this.missingOrInvalidParams.add(reqParam); 80 | } 81 | } 82 | 83 | return this.missingOrInvalidParams == null || missingOrInvalidParams.isEmpty(); 84 | } 85 | 86 | 87 | /** 88 | * Used to check if the parameter not null and not empty 89 | */ 90 | public boolean has(final String paramKey) { 91 | final String paramValue = this.request.getParameter(paramKey); 92 | return paramValue != null && !paramValue.trim().isEmpty(); 93 | } 94 | 95 | public long getLongParameter(String key) { 96 | final String value = getStringParameter(key); 97 | return value != null ? Long.parseLong(value) : -1; 98 | } 99 | 100 | public static class RequestException extends Exception { 101 | public RequestException(String message) { 102 | super(message); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /api/web/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /api/web/jaan_kesi.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/api/web/jaan_kesi.mp3 -------------------------------------------------------------------------------- /api/web/status.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.util.List" %> 2 | <%@ page import="com.theah64.scd.models.User" %> 3 | <%@ page import="com.theah64.scd.database.tables.Users" %> 4 | <%@ page import="com.theah64.scd.models.SCClient" %> 5 | <%@ page import="com.theah64.scd.database.tables.SCClients" %><%-- 6 | Created by IntelliJ IDEA. 7 | User: theapache64 8 | Date: 8/12/16 9 | Time: 6:36 PM 10 | To change this template use File | Settings | File Templates. 11 | --%> 12 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 13 | 14 | 15 | API Status 16 | 17 | 18 | 19 | 20 | 21 | 22 | <%-- 23 | final String id = rs.getString(COLUMN_ID); 24 | final String name = rs.getString(COLUMN_NAME); 25 | final String imei = rs.getString(COLUMN_IMEI); 26 | final String email = rs.getString(COLUMN_EMAIL); 27 | final long totalRequests = rs.getLong(COLUMN_AS_TOTAL_REQUESTS); 28 | final long totalDownloads = rs.getLong(COLUMN_AS_TOTAL_DOWNLOADS); 29 | final long totalTracks = rs.getLong(COLUMN_AS_TOTAL_TRACKS); 30 | final String lastHit = rs.getString(COLUMN_AS_LAST_HIT); 31 | final boolean isActive = rs.getBoolean(COLUMN_IS_ACTIVE);--%> 32 |
33 |

SoundCloudDownloader

34 |

User statistics

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | <% 52 | final List users = Users.getInstance().getAll(); 53 | if (users != null) { 54 | for (final User user : users) { 55 | %> 56 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 76 | <% 77 | } 78 | } 79 | %> 80 | 81 |
IDNameIMEIEMailTotal requestsTotal downloadsTotal tracksLast hitisActive
<%=user.getId()%> 58 | <%=user.getName()%> 60 | <%=user.getIMEI()%> 62 | <%=user.getEmail()%> 64 | <%=user.getTotalRequests()%> 66 | <%=user.getTotalDownloads()%> 68 | <%=user.getTotalTracks()%> 70 | Click here 72 | <%=user.isActive() ? "YES" : "NO"%> 74 |
82 | 83 |

Client statistics

84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | <% 96 | final List clients = SCClients.getInstance().getAll(); 97 | if (clients != null) { 98 | for (final SCClient scClient : clients) { 99 | %> 100 | 101 | 104 | 105 | 108 | 109 | 112 | 113 | 116 | 117 | 118 | <% 119 | } 120 | } 121 | %> 122 | 123 |
IDNameTotal hitsisActive
102 | <%=scClient.getId()%> 103 | 106 | <%=scClient.getName()%> 107 | 110 | <%=scClient.getTotalHits()%> 111 | 114 | <%=scClient.isActive()%> 115 |
124 | 125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /client/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /client/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /client/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /client/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/.idea/dictionaries/theapache64.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | igniter 5 | playlists 6 | snackbar 7 | soundcloud 8 | soundclouddownloader 9 | zound 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /client/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /client/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /client/.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /client/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /client/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /client/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion '27.0.3' 6 | defaultConfig { 7 | applicationId "com.theah64.soundclouddownloader" 8 | minSdkVersion 14 9 | targetSdkVersion 27 10 | versionCode 1010 11 | versionName "1.0.10" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | signingConfig getSigningConfig() 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | 29 | compile 'com.android.support:appcompat-v7:27.1.0' 30 | compile 'com.android.support:design:27.1.0' 31 | compile 'com.squareup.okhttp3:okhttp:3.9.1' 32 | compile 'org.jetbrains:annotations-java5:15.0' 33 | compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' 34 | compile 'com.android.support:support-v4:27.1.0' 35 | compile 'com.mpatric:mp3agic:0.8.1' 36 | compile 'com.theah64.bugmailer:bugmailer:3.0.0' 37 | implementation 'com.karumi:dexter:4.2.0' 38 | testCompile 'junit:junit:4.12' 39 | } 40 | 41 | 42 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /client/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "607760617513", 4 | "firebase_url": "https://soundcloud-downloader-53706.firebaseio.com", 5 | "project_id": "soundcloud-downloader-53706", 6 | "storage_bucket": "soundcloud-downloader-53706.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:607760617513:android:804de5ebfa8ba704", 12 | "android_client_info": { 13 | "package_name": "com.theah64.soundclouddownloader" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "607760617513-rre61jo78s0jbs9gt7mgvgp2hmptvalj.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.theah64.soundclouddownloader", 22 | "certificate_hash": "243F99B5BBF8E02A8883BDC245CF61B8A6155C36" 23 | } 24 | }, 25 | { 26 | "client_id": "607760617513-q0j0ij5ucppebmn11bmp56c71eakt6e0.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyDrptNHFk-h7i1ohoWkaf7WaAckfiREsWk" 33 | } 34 | ], 35 | "services": { 36 | "analytics_service": { 37 | "status": 1 38 | }, 39 | "appinvite_service": { 40 | "status": 2, 41 | "other_platform_oauth_client": [ 42 | { 43 | "client_id": "607760617513-q0j0ij5ucppebmn11bmp56c71eakt6e0.apps.googleusercontent.com", 44 | "client_type": 3 45 | } 46 | ] 47 | }, 48 | "ads_service": { 49 | "status": 2 50 | } 51 | } 52 | } 53 | ], 54 | "configuration_version": "1" 55 | } -------------------------------------------------------------------------------- /client/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/theapache64/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /client/app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/release/app-release.apk -------------------------------------------------------------------------------- /client/app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1010,"versionName":"1.0.10","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /client/app/soundclouddownloader.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/soundclouddownloader.apk -------------------------------------------------------------------------------- /client/app/src/androidTest/java/com/theah64/musicdog/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.theah64.musicdog; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.theah64.soundclouddownloader", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/app/src/main/assets/database.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS playlists; 2 | CREATE TABLE playlists( 3 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | title VARCHAR(50) NOT NULL, 5 | username VARCHAR(100) NOT NULL, 6 | soundcloud_url TEXT NOT NULL, 7 | artwork_url TEXT DEFAULT NULL, 8 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 9 | ); 10 | 11 | DROP TABLE IF EXISTS tracks; 12 | CREATE TABLE tracks( 13 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 14 | title TEXT NOT NULL, 15 | username VARCHAR(100) NOT NULL, 16 | duration INTEGER NOT NULL, 17 | soundcloud_url TEXT NOT NULL, 18 | download_url TEXT NOT NULL, 19 | artwork_url TEXT DEFAULT NULL, 20 | abs_file_path TEXT NOT NULL, 21 | is_downloaded INTEGER CHECK(is_downloaded IN (0,1)) NOT NULL, 22 | playlist_id INTEGER DEFAULT NULL, 23 | download_id INTEGER DEFAULT NULL, 24 | created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 25 | FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON UPDATE CASCADE ON DELETE CASCADE 26 | ); 27 | -------------------------------------------------------------------------------- /client/app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/adapters/PlaylistDownloadAdapter.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.adapters; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.CheckBox; 8 | import android.widget.CompoundButton; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.nostra13.universalimageloader.core.ImageLoader; 13 | import com.theah64.soundclouddownloader.R; 14 | import com.theah64.soundclouddownloader.models.ITSNode; 15 | import com.theah64.soundclouddownloader.models.Track; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * Created by theapache64 on 10/12/16. 21 | */ 22 | 23 | public class PlaylistDownloadAdapter extends RecyclerView.Adapter { 24 | 25 | private static final String X = PlaylistDownloadAdapter.class.getSimpleName(); 26 | private final List tracks; 27 | private final PlaylistListener callback; 28 | private LayoutInflater inflater; 29 | 30 | public PlaylistDownloadAdapter(List tracks, PlaylistListener callback) { 31 | this.tracks = tracks; 32 | this.callback = callback; 33 | } 34 | 35 | @Override 36 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 37 | 38 | if (inflater == null) { 39 | inflater = LayoutInflater.from(parent.getContext()); 40 | } 41 | 42 | final View row = inflater.inflate(R.layout.playlist_row, parent, false); 43 | return new ViewHolder(row); 44 | } 45 | 46 | @Override 47 | public void onBindViewHolder(ViewHolder holder, int position) { 48 | final ITSNode track = tracks.get(position); 49 | 50 | holder.tvTitle.setText(track.getTitle()); 51 | holder.cbDownload.setChecked(track.isChecked()); 52 | holder.tvSubtitle1.setText(track.getSubtitle1()); 53 | holder.tvSubtitle2.setText(track.getSubtitle2()); 54 | holder.tvSubtitle3.setText(track.getSubtitle3(null)); 55 | 56 | ImageLoader.getInstance().displayImage(track.getArtworkUrl(), holder.ivArtwork); 57 | 58 | 59 | } 60 | 61 | @Override 62 | public int getItemCount() { 63 | return tracks.size(); 64 | } 65 | 66 | 67 | public interface PlaylistListener { 68 | void onChecked(int position); 69 | 70 | void onUnChecked(int position); 71 | } 72 | 73 | class ViewHolder extends RecyclerView.ViewHolder { 74 | 75 | final ImageView ivArtwork; 76 | final CheckBox cbDownload; 77 | final TextView tvSubtitle1, tvSubtitle2, tvTitle, tvSubtitle3; 78 | 79 | ViewHolder(View itemView) { 80 | super(itemView); 81 | 82 | this.cbDownload = (CheckBox) itemView.findViewById(R.id.cbDownload); 83 | this.ivArtwork = (ImageView) itemView.findViewById(R.id.ivArtwork); 84 | this.tvTitle = (TextView) itemView.findViewById(R.id.tvTitle); 85 | this.tvSubtitle1 = (TextView) itemView.findViewById(R.id.tvSubtitle1); 86 | this.tvSubtitle2 = (TextView) itemView.findViewById(R.id.tvSubtitle2); 87 | this.tvSubtitle3 = (TextView) itemView.findViewById(R.id.tvSubtitle3); 88 | 89 | itemView.setOnClickListener(new View.OnClickListener() { 90 | @Override 91 | public void onClick(View view) { 92 | cbDownload.setChecked(!cbDownload.isChecked()); 93 | } 94 | }); 95 | 96 | this.cbDownload.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 97 | @Override 98 | public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { 99 | if (isChecked) { 100 | callback.onChecked(getLayoutPosition()); 101 | } else { 102 | callback.onUnChecked(getLayoutPosition()); 103 | } 104 | } 105 | }); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/adapters/TracksAndPlaylistsViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.adapters; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentPagerAdapter; 7 | 8 | import com.theah64.soundclouddownloader.database.Playlists; 9 | import com.theah64.soundclouddownloader.database.Tracks; 10 | 11 | import java.util.Locale; 12 | 13 | /** 14 | * Created by theapache64 on 12/12/16. 15 | */ 16 | 17 | public class TracksAndPlaylistsViewPagerAdapter extends FragmentPagerAdapter { 18 | 19 | private final Context context; 20 | private final Fragment tracksFragment, playlistsFragment; 21 | 22 | public TracksAndPlaylistsViewPagerAdapter(FragmentManager fm, Context context, Fragment tracksFragment, Fragment playlistsFragment) { 23 | super(fm); 24 | this.context = context; 25 | this.tracksFragment = tracksFragment; 26 | this.playlistsFragment = playlistsFragment; 27 | } 28 | 29 | @Override 30 | public Fragment getItem(int position) { 31 | return position == 0 ? tracksFragment : playlistsFragment; 32 | } 33 | 34 | @Override 35 | public int getCount() { 36 | return 2; 37 | } 38 | 39 | @Override 40 | public CharSequence getPageTitle(int position) { 41 | return String.format(Locale.getDefault(), "%s (%d)", position == 0 ? "TRACKS" : "PLAYLISTS", position == 0 ? Tracks.getInstance(context).getCount() : Playlists.getInstance(context).getCount()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/interfaces/MainActivityCallback.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.interfaces; 2 | 3 | /** 4 | * Created by theapache64 on 17/12/16. 5 | */ 6 | 7 | public interface MainActivityCallback { 8 | void onRemovePlaylistTrack(String playlistId); 9 | 10 | void onRemovePlaylist(String playlistId); 11 | 12 | void setTabTracksCount(int count); 13 | 14 | void setTabPlaylistsCount(int count); 15 | 16 | void hideFabAdd(); 17 | 18 | void showFabAdd(); 19 | 20 | boolean isFabAddShown(); 21 | } 22 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/interfaces/PlaylistListener.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.interfaces; 2 | 3 | import com.theah64.soundclouddownloader.models.Playlist; 4 | 5 | /** 6 | * Created by theapache64 on 17/12/16. 7 | */ 8 | 9 | public interface PlaylistListener { 10 | void onPlaylistUpdated(String playlistId); 11 | 12 | void onNewPlaylist(Playlist playlist); 13 | } 14 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/interfaces/TrackListener.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.interfaces; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.theah64.soundclouddownloader.models.Track; 6 | 7 | /** 8 | * Created by theapache64 on 16/12/16. 9 | */ 10 | 11 | public interface TrackListener { 12 | void onNewTrack(Track newTrack); 13 | 14 | void onTrackRemoved(@NonNull Track removedTrack); 15 | 16 | void onTrackUpdated(@NonNull Track updatedTrack); 17 | } 18 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/models/ITSNode.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.models; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import com.theah64.soundclouddownloader.utils.DownloadUtils; 6 | 7 | /** 8 | * Created by theapache64 on 11/12/16. 9 | */ 10 | 11 | public interface ITSNode { 12 | 13 | String getArtworkUrl(); 14 | 15 | String getTitle(); 16 | 17 | String getSubtitle1(); 18 | 19 | String getSubtitle2(); 20 | 21 | String getSubtitle3(@Nullable DownloadUtils downloadUtils); 22 | 23 | boolean isChecked(); 24 | } 25 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/models/Playlist.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.models; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import com.theah64.soundclouddownloader.utils.CommonUtils; 6 | import com.theah64.soundclouddownloader.utils.DownloadUtils; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by theapache64 on 12/12/16. 13 | */ 14 | public class Playlist implements ITSNode, Serializable { 15 | 16 | public static final String KEY = "playlist"; 17 | private static final String SOUND_CLOUD_PLAYLIST_REGEX = "^(?:https:\\/\\/|http:\\/\\/|www\\.|)soundcloud\\.com\\/(?:.+)\\/sets\\/(?:.+)$"; 18 | private final String username; 19 | private final String title; 20 | private final String sanitizedTitle; 21 | private final String url; 22 | private final String artworkUrl; 23 | private int totalTracks; 24 | private final int tracksDownloaded; 25 | private final String totalDurationInHHMMSS; 26 | private String id; 27 | private List tracks; 28 | 29 | public Playlist(String id, String title, String username, String url, String artworkUrl, int totalTracks, int tracksDownloaded, long totalDuration) { 30 | this.id = id; 31 | this.title = title; 32 | this.username = username; 33 | this.url = url; 34 | this.artworkUrl = artworkUrl; 35 | this.totalTracks = totalTracks; 36 | this.tracksDownloaded = tracksDownloaded; 37 | this.totalDurationInHHMMSS = Track.calculateHMS(totalDuration); 38 | this.sanitizedTitle = getSanitizedTitle(title); 39 | } 40 | 41 | private static String getSanitizedTitle(String title) { 42 | return CommonUtils.getSanitizedName(title); 43 | } 44 | 45 | public static boolean isPlaylist(String soundCloudUrl) { 46 | return soundCloudUrl.matches(SOUND_CLOUD_PLAYLIST_REGEX); 47 | } 48 | 49 | public String getSanitizedTitle() { 50 | return sanitizedTitle; 51 | } 52 | 53 | public String getUsername() { 54 | return username; 55 | } 56 | 57 | public String getId() { 58 | return id; 59 | } 60 | 61 | public void setId(String id) { 62 | this.id = id; 63 | } 64 | 65 | @Override 66 | public String getArtworkUrl() { 67 | return artworkUrl; 68 | } 69 | 70 | public String getTitle() { 71 | return title; 72 | } 73 | 74 | @Override 75 | public String getSubtitle1() { 76 | return username; 77 | } 78 | 79 | @Override 80 | public String getSubtitle2() { 81 | return totalDurationInHHMMSS; 82 | } 83 | 84 | @Override 85 | public String getSubtitle3(@Nullable DownloadUtils downloadUtils) { 86 | return tracksDownloaded + " (saved) /" + totalTracks + " (total)"; 87 | } 88 | 89 | @Override 90 | public boolean isChecked() { 91 | return false; 92 | } 93 | 94 | public String getSoundCloudUrl() { 95 | return url; 96 | } 97 | 98 | public int getTotalTracks() { 99 | return totalTracks; 100 | } 101 | 102 | public boolean isDownloaded() { 103 | return tracksDownloaded == totalTracks; 104 | } 105 | 106 | public List getTracks() { 107 | return tracks; 108 | } 109 | 110 | public void setTracks(List tracks) { 111 | this.tracks = tracks; 112 | } 113 | 114 | 115 | public void setTotalTracks(int totalTracks) { 116 | this.totalTracks = totalTracks; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/receivers/OnBootCompleted.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.receivers; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.util.Log; 8 | 9 | import com.theah64.soundclouddownloader.services.ClipboardWatchIgniterService; 10 | import com.theah64.soundclouddownloader.utils.CommonUtils; 11 | 12 | public class OnBootCompleted extends BroadcastReceiver { 13 | 14 | private static final String X = OnBootCompleted.class.getSimpleName(); 15 | 16 | public OnBootCompleted() { 17 | } 18 | 19 | @Override 20 | public void onReceive(Context context, Intent intent) { 21 | Log.d(X, "Boot finished"); 22 | 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && !CommonUtils.isMyServiceRunning(context, ClipboardWatchIgniterService.class)) { 24 | //Supports clipboard listener 25 | context.startService(new Intent(context, ClipboardWatchIgniterService.class)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/services/ClipboardWatchIgniterService.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.services; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationManager; 5 | import android.app.PendingIntent; 6 | import android.app.Service; 7 | import android.content.ClipboardManager; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.graphics.BitmapFactory; 11 | import android.os.Build; 12 | import android.os.IBinder; 13 | import android.support.annotation.RequiresApi; 14 | import android.support.v4.app.NotificationCompat; 15 | import android.util.Log; 16 | 17 | import com.theah64.soundclouddownloader.R; 18 | import com.theah64.soundclouddownloader.database.Tracks; 19 | import com.theah64.soundclouddownloader.models.Playlist; 20 | import com.theah64.soundclouddownloader.utils.ClipboardUtils; 21 | import com.theah64.soundclouddownloader.utils.Random; 22 | 23 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 24 | public class ClipboardWatchIgniterService extends Service implements ClipboardManager.OnPrimaryClipChangedListener { 25 | private static final String X = ClipboardWatchIgniterService.class.getSimpleName(); 26 | 27 | public ClipboardWatchIgniterService() { 28 | } 29 | 30 | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) 31 | @Override 32 | public int onStartCommand(Intent intent, int flags, int startId) { 33 | Log.d(X, "Registering new clipboard watcher..."); 34 | ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).addPrimaryClipChangedListener(this); 35 | return START_STICKY; 36 | } 37 | 38 | @Override 39 | public IBinder onBind(Intent intent) { 40 | // TODO: Return the communication channel to the service. 41 | throw new UnsupportedOperationException("Not yet implemented"); 42 | } 43 | 44 | @Override 45 | public void onPrimaryClipChanged() { 46 | Log.d(X, "Clipboard changed..."); 47 | final String soundCloudUrl = ClipboardUtils.getSoundCloudUrl(this); 48 | 49 | if (soundCloudUrl != null) { 50 | 51 | final int notifId = Random.getRandomInt(); 52 | final String title = getString(R.string.Do_you_want_to_download_this_s, Playlist.isPlaylist(soundCloudUrl) ? "playlist" : "track"); 53 | 54 | final Intent yesIntent = new Intent(this, DownloaderService.class); 55 | yesIntent.putExtra(Tracks.COLUMN_SOUNDCLOUD_URL, soundCloudUrl); 56 | yesIntent.putExtra(DownloaderService.KEY_NOTIFICATION_ID, notifId); 57 | 58 | final PendingIntent downloadIntent = PendingIntent.getService(this, 1, yesIntent, PendingIntent.FLAG_UPDATE_CURRENT); 59 | final PendingIntent dismissIntent = DownloaderService.getDismissIntent(notifId, this); 60 | 61 | final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 62 | final NotificationCompat.Builder notBuilder = new NotificationCompat.Builder(this) 63 | .setPriority(NotificationCompat.PRIORITY_MAX) 64 | .setTicker(title) 65 | .setSmallIcon(R.drawable.ic_stat_logo_white) 66 | .setStyle(new NotificationCompat.BigTextStyle()) 67 | .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_logo_24dp)) 68 | .setContentTitle(title) 69 | .setDefaults(Notification.DEFAULT_ALL) 70 | .setAutoCancel(true) 71 | .addAction(R.drawable.ic_thumb_up_white_24dp, getString(R.string.YES), downloadIntent) 72 | .addAction(R.drawable.ic_thumb_down_white_24dp, getString(R.string.NO), dismissIntent) 73 | .setContentText(soundCloudUrl); 74 | 75 | nm.notify(notifId, notBuilder.build()); 76 | 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/BaseAppCompatActivity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities; 2 | 3 | import android.os.Bundle; 4 | import android.os.Parcelable; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.util.Log; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.io.Serializable; 15 | 16 | /** 17 | * Created by theapache64 on 10/12/16. 18 | */ 19 | 20 | public abstract class BaseAppCompatActivity extends AppCompatActivity { 21 | public static final String FATAL_ERROR_NO_SERIALIZABLE_FOUND_WITH_KEY_S = "No serializable found with key %s"; 22 | protected static final String FATAL_ERROR_FORGOT_TO_HANDLE = "Forgot to handle"; 23 | private static final String FATAL_ERROR_S_IS_MISSING = "%s is missing"; 24 | private static final String X = BaseAppCompatActivity.class.getSimpleName(); 25 | public Menu menu; 26 | 27 | @Override 28 | protected void onCreate(@Nullable Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | } 31 | 32 | public abstract boolean isSecureActivity(); 33 | 34 | @Override 35 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 36 | super.setSupportActionBar(toolbar); 37 | assert toolbar != null; 38 | //RobotoUtils.getInstance(getAssets()).robotize(toolbar, RobotoUtils.ROBOTO_REGULAR_INDEX); 39 | } 40 | 41 | 42 | @NotNull 43 | protected final String getStringOrThrow(final String key) { 44 | final String value = getIntent().getStringExtra(key); 45 | if (value == null) { 46 | throw new IllegalArgumentException(String.format(FATAL_ERROR_S_IS_MISSING, key)); 47 | } 48 | return value; 49 | } 50 | 51 | 52 | protected int getIntOrThrow(String key) { 53 | final int value = getIntent().getIntExtra(key, -1); 54 | if (value == -1) { 55 | throw new IllegalArgumentException(String.format(FATAL_ERROR_NO_SERIALIZABLE_FOUND_WITH_KEY_S, key)); 56 | } 57 | return value; 58 | } 59 | 60 | @NotNull 61 | protected Serializable getSerializableOrThrow(String key) { 62 | final Serializable ob = getIntent().getSerializableExtra(key); 63 | if (ob == null) { 64 | throw new IllegalArgumentException(String.format(FATAL_ERROR_NO_SERIALIZABLE_FOUND_WITH_KEY_S, key)); 65 | } 66 | return ob; 67 | } 68 | 69 | @NotNull 70 | protected Parcelable getParcelableOrThrow(String key) { 71 | final Parcelable ob = getIntent().getParcelableExtra(key); 72 | if (ob == null) { 73 | throw new IllegalArgumentException(String.format("No parcelable found with the key %s", key)); 74 | } 75 | return ob; 76 | } 77 | 78 | //Enabling back navigation 79 | @Override 80 | public boolean onOptionsItemSelected(MenuItem item) { 81 | 82 | Log.d(X, "Back button pressed"); 83 | 84 | if (item.getItemId() == android.R.id.home) { 85 | //Close this activity 86 | finish(); 87 | return true; 88 | } else { 89 | return super.onOptionsItemSelected(item); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/DownloaderActivity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import com.theah64.soundclouddownloader.R; 8 | import com.theah64.soundclouddownloader.utils.DownloadIgniter; 9 | 10 | public class DownloaderActivity extends AppCompatActivity { 11 | 12 | 13 | private static final String X = DownloaderActivity.class.getSimpleName(); 14 | 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_downloader); 20 | 21 | final String data = getIntent().getStringExtra(Intent.EXTRA_TEXT); 22 | DownloadIgniter.ignite(this, data); 23 | 24 | finish(); 25 | 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/PlaylistTracksActivity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.ActionBar; 5 | import android.support.v7.widget.Toolbar; 6 | 7 | import com.theah64.soundclouddownloader.R; 8 | import com.theah64.soundclouddownloader.interfaces.MainActivityCallback; 9 | import com.theah64.soundclouddownloader.models.Playlist; 10 | import com.theah64.soundclouddownloader.ui.fragments.TracksFragment; 11 | 12 | public class PlaylistTracksActivity extends BaseAppCompatActivity implements MainActivityCallback { 13 | 14 | private static final String X = PlaylistTracksActivity.class.getSimpleName(); 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_playlist_tracks); 20 | 21 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 22 | setSupportActionBar(toolbar); 23 | 24 | final Playlist playlist = (Playlist) getSerializableOrThrow(Playlist.KEY); 25 | 26 | final ActionBar actionBar = getSupportActionBar(); 27 | assert actionBar != null; 28 | actionBar.setDisplayHomeAsUpEnabled(true); 29 | actionBar.setTitle(playlist.getTitle()); 30 | actionBar.setSubtitle(getResources().getQuantityString(R.plurals.d_tracks, playlist.getTotalTracks(), playlist.getTotalTracks())); 31 | 32 | final TracksFragment tracksFragment = TracksFragment.getNewInstance(playlist.getId()); 33 | getSupportFragmentManager().beginTransaction().replace(R.id.flPlaylistTracksContainer, tracksFragment).commit(); 34 | } 35 | 36 | @Override 37 | public boolean isSecureActivity() { 38 | return true; 39 | } 40 | 41 | 42 | @Override 43 | public void onRemovePlaylistTrack(String playlistId) { 44 | 45 | } 46 | 47 | @Override 48 | public void onRemovePlaylist(String playlistId) { 49 | 50 | } 51 | 52 | @Override 53 | public void setTabTracksCount(int count) { 54 | 55 | } 56 | 57 | @Override 58 | public void setTabPlaylistsCount(int count) { 59 | 60 | } 61 | 62 | @Override 63 | public void hideFabAdd() { 64 | 65 | } 66 | 67 | @Override 68 | public void showFabAdd() { 69 | 70 | } 71 | 72 | @Override 73 | public boolean isFabAddShown() { 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities; 2 | 3 | import android.Manifest; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.os.Bundle; 8 | import android.os.Handler; 9 | import android.provider.Settings; 10 | import android.support.v7.app.AlertDialog; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.widget.TextView; 13 | 14 | import com.karumi.dexter.Dexter; 15 | import com.karumi.dexter.MultiplePermissionsReport; 16 | import com.karumi.dexter.PermissionToken; 17 | import com.karumi.dexter.listener.PermissionRequest; 18 | import com.karumi.dexter.listener.multi.MultiplePermissionsListener; 19 | import com.theah64.soundclouddownloader.BuildConfig; 20 | import com.theah64.soundclouddownloader.R; 21 | import com.theah64.soundclouddownloader.utils.PrefUtils; 22 | 23 | import java.util.List; 24 | 25 | public class SplashActivity extends AppCompatActivity { 26 | 27 | private static final long SPLASH_DELAY = 1500; 28 | public static final String KEY_IS_APP_STARTED = "is_app_started"; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_splash); 34 | 35 | 36 | ((TextView) findViewById(R.id.tvAppVersion)).setText(String.format("v%s", BuildConfig.VERSION_NAME)); 37 | 38 | Dexter.withActivity(this) 39 | .withPermissions( 40 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 41 | Manifest.permission.GET_ACCOUNTS, 42 | Manifest.permission.READ_PHONE_STATE, 43 | Manifest.permission.READ_CONTACTS) 44 | .withListener(new MultiplePermissionsListener() { 45 | @Override 46 | public void onPermissionsChecked(MultiplePermissionsReport report) { 47 | 48 | if (report.areAllPermissionsGranted()) { 49 | doNormalSplashWork(); 50 | } else { 51 | showSettingsDialog(); 52 | } 53 | } 54 | 55 | @Override 56 | public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) { 57 | token.continuePermissionRequest(); 58 | } 59 | }) 60 | .onSameThread() 61 | .check(); 62 | } 63 | 64 | 65 | private void showSettingsDialog() { 66 | AlertDialog.Builder builder = new AlertDialog.Builder(SplashActivity.this); 67 | builder.setTitle(R.string.Insufficient_permissions); 68 | builder.setMessage(R.string.Permission_instructions); 69 | builder.setPositiveButton(R.string.SETTINGS, new DialogInterface.OnClickListener() { 70 | @Override 71 | public void onClick(DialogInterface dialog, int which) { 72 | dialog.cancel(); 73 | openSettings(); 74 | finish(); 75 | } 76 | }); 77 | builder.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { 78 | @Override 79 | public void onClick(DialogInterface dialog, int which) { 80 | dialog.cancel(); 81 | finish(); 82 | } 83 | }); 84 | builder.show(); 85 | 86 | } 87 | 88 | private void openSettings() { 89 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 90 | Uri uri = Uri.fromParts("package", getPackageName(), null); 91 | intent.setData(uri); 92 | startActivityForResult(intent, 101); 93 | } 94 | 95 | 96 | private void doNormalSplashWork() { 97 | 98 | //Setting permission flag to true 99 | PrefUtils.getInstance(this).getEditor().putBoolean(KEY_IS_APP_STARTED, true).commit(); 100 | 101 | 102 | //Checking if the api key exists 103 | new Handler().postDelayed(new Runnable() { 104 | @Override 105 | public void run() { 106 | 107 | 108 | startActivity(new Intent(SplashActivity.this, MainActivity.class)); 109 | finish(); 110 | 111 | } 112 | }, SPLASH_DELAY); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/TestActivity.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.theah64.soundclouddownloader.R; 7 | 8 | public class TestActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_test); 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/activities/settings/SettingsActivityCompat.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.activities.settings; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Bundle; 5 | import android.preference.Preference; 6 | import android.preference.PreferenceActivity; 7 | import android.preference.PreferenceManager; 8 | import android.support.annotation.LayoutRes; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.app.AppCompatDelegate; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.View; 13 | 14 | import com.theah64.soundclouddownloader.R; 15 | 16 | /** 17 | * This class used as a compatible version of SettingsActivity 18 | * Created by theapache64 on 29/2/16. 19 | */ 20 | @SuppressWarnings("deprecation") 21 | public class SettingsActivityCompat extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener, Preference.OnPreferenceClickListener { 22 | 23 | public static final String X = SettingsActivityCompat.class.getSimpleName(); 24 | private SharedPreferences defaultSharedPref; 25 | private AppCompatDelegate mDelegate; 26 | 27 | 28 | @Override 29 | protected void onPostCreate(Bundle savedInstanceState) { 30 | super.onPostCreate(savedInstanceState); 31 | getDelegate().onPostCreate(savedInstanceState); 32 | } 33 | 34 | @Override 35 | public void setContentView(@LayoutRes int layoutResID) { 36 | getDelegate().setContentView(layoutResID); 37 | } 38 | 39 | @Override 40 | protected void onPostResume() { 41 | super.onPostResume(); 42 | getDelegate().onPostResume(); 43 | } 44 | 45 | @Override 46 | protected void onStop() { 47 | super.onStop(); 48 | getDelegate().onStop(); 49 | } 50 | 51 | @Override 52 | protected void onDestroy() { 53 | super.onDestroy(); 54 | getDelegate().onDestroy(); 55 | } 56 | 57 | private void setSupportActionBar(@Nullable Toolbar toolbar) { 58 | getDelegate().setSupportActionBar(toolbar); 59 | } 60 | 61 | private AppCompatDelegate getDelegate() { 62 | if (mDelegate == null) { 63 | mDelegate = AppCompatDelegate.create(this, null); 64 | } 65 | return mDelegate; 66 | } 67 | 68 | @Override 69 | public void onCreate(Bundle savedInstanceState) { 70 | getDelegate().installViewFactory(); 71 | getDelegate().onCreate(savedInstanceState); 72 | super.onCreate(savedInstanceState); 73 | setContentView(R.layout.activity_settings_2); 74 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 75 | setSupportActionBar(toolbar); 76 | toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp); 77 | toolbar.setNavigationOnClickListener(new View.OnClickListener() { 78 | @Override 79 | public void onClick(View v) { 80 | finish(); 81 | } 82 | }); 83 | addPreferencesFromResource(R.xml.settings_screen); 84 | this.defaultSharedPref = PreferenceManager.getDefaultSharedPreferences(this); 85 | } 86 | 87 | 88 | @Override 89 | public void onResume() { 90 | super.onResume(); 91 | 92 | //Log.d(X, "registering listener"); 93 | defaultSharedPref.registerOnSharedPreferenceChangeListener(this); 94 | } 95 | 96 | 97 | @Override 98 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) { 99 | 100 | } 101 | 102 | //Clicked on preference item. 103 | @Override 104 | public boolean onPreferenceClick(Preference preference) { 105 | return SettingsActivity.SettingsFragment.onCompatPreferenceClick(this, preference); 106 | } 107 | 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/ui/fragments/BaseMusicFragment.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.ui.fragments; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.support.v4.app.Fragment; 6 | 7 | /** 8 | * Created by theapache64 on 15/12/16. 9 | */ 10 | 11 | public abstract class BaseMusicFragment extends Fragment { 12 | 13 | protected void openSoundCloud() { 14 | final Intent soundCloudIntent = new Intent(Intent.ACTION_VIEW); 15 | soundCloudIntent.setData(Uri.parse("http://soundcloud.com")); 16 | startActivity(soundCloudIntent); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/APIResponse.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.support.annotation.StringRes; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | /** 9 | * Created by shifar on 23/7/16. 10 | */ 11 | public class APIResponse { 12 | 13 | private final String message; 14 | private final JSONObject joMain; 15 | 16 | public APIResponse(final String stringResp) throws APIException, JSONException { 17 | 18 | joMain = new JSONObject(stringResp); 19 | this.message = joMain.getString("message"); 20 | 21 | if (joMain.getBoolean("error")) { 22 | final int errorCode = joMain.getInt("error_code"); 23 | throw new APIException(errorCode, message); 24 | } 25 | 26 | } 27 | 28 | public JSONObject getJSONObjectData() throws JSONException { 29 | return joMain.getJSONObject("data"); 30 | } 31 | 32 | 33 | public String getMessage() { 34 | return this.message; 35 | } 36 | 37 | public static class APIException extends Exception { 38 | 39 | private final int pleasantMsg; 40 | 41 | APIException(final int errorCode, String msg) { 42 | super(msg); 43 | pleasantMsg = getPleasantMessage(errorCode); 44 | } 45 | 46 | private static 47 | @StringRes 48 | int getPleasantMessage(int errorCode) { 49 | 50 | switch (errorCode) { 51 | 52 | default: 53 | return -1; 54 | } 55 | 56 | } 57 | 58 | @StringRes 59 | public int getPleasantMsg() { 60 | return pleasantMsg; 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/App.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.os.Environment; 7 | 8 | import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator; 9 | import com.nostra13.universalimageloader.core.DisplayImageOptions; 10 | import com.nostra13.universalimageloader.core.ImageLoader; 11 | import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 12 | import com.nostra13.universalimageloader.core.assist.ImageScaleType; 13 | import com.nostra13.universalimageloader.core.assist.QueueProcessingType; 14 | import com.theah64.bugmailer.core.BugMailer; 15 | import com.theah64.bugmailer.core.BugMailerConfig; 16 | import com.theah64.bugmailer.exceptions.BugMailerException; 17 | import com.theah64.soundclouddownloader.R; 18 | import com.theah64.soundclouddownloader.interfaces.PlaylistListener; 19 | import com.theah64.soundclouddownloader.interfaces.TrackListener; 20 | 21 | import java.io.File; 22 | 23 | /** 24 | * Created by theapache64 on 9/12/16. 25 | */ 26 | public class App extends Application { 27 | 28 | public static final String APK_DOWNLOAD_URL = "http://tinyurl.com/soundclouddownloader-apk"; 29 | private static final String FOLDER_NAME = "SoundCloud Downloader"; 30 | public static final String GITHUB_URL = "https://github.com/theapache64/soundcloud-downloader"; 31 | public static final boolean IS_DEBUG_MODE = false; 32 | private static String DEFAULT_STORAGE_LOCATION; 33 | private TrackListener mainTrackListener = null, playlistTrackListener = null; 34 | private PlaylistListener playlistListener = null; 35 | 36 | private static void initImageLoader(final Context context) { 37 | 38 | ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(context); 39 | config.threadPriority(Thread.NORM_PRIORITY - 2); 40 | config.denyCacheImageMultipleSizesInMemory(); 41 | 42 | final DisplayImageOptions defaultImageOption = new DisplayImageOptions.Builder() 43 | .cacheOnDisk(true) 44 | .cacheInMemory(true) 45 | .considerExifParams(true) 46 | .showImageOnLoading(R.drawable.ic_spinner_grey_24dp) 47 | .showImageForEmptyUri(R.drawable.ic_av_album_70dp) 48 | .showImageOnFail(R.drawable.ic_av_album_70dp) 49 | .imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2) 50 | .bitmapConfig(Bitmap.Config.RGB_565) 51 | .build(); 52 | 53 | config.defaultDisplayImageOptions(defaultImageOption); 54 | 55 | config.diskCacheFileNameGenerator(new Md5FileNameGenerator()); 56 | config.diskCacheSize(100 * 1024 * 1024); // 100 MiB 57 | config.memoryCacheSize(50 * 1024 * 1024); 58 | config.tasksProcessingOrder(QueueProcessingType.LIFO); 59 | config.writeDebugLogs(); // Remove for release app 60 | 61 | // Initialize ImageLoader with configuration. 62 | ImageLoader.getInstance().init(config.build()); 63 | } 64 | 65 | public static String getDefaultStorageLocation() { 66 | return DEFAULT_STORAGE_LOCATION; 67 | } 68 | 69 | @Override 70 | public void onCreate() { 71 | super.onCreate(); 72 | initImageLoader(this); 73 | DEFAULT_STORAGE_LOCATION = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) + File.separator + FOLDER_NAME; 74 | 75 | try { 76 | BugMailer.init(this, new BugMailerConfig("theapache64@gmail.com")); 77 | } catch (BugMailerException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | 82 | public TrackListener getMainTrackListener() { 83 | return mainTrackListener; 84 | } 85 | 86 | public void setMainTrackListener(TrackListener mainTrackListener) { 87 | this.mainTrackListener = mainTrackListener; 88 | } 89 | 90 | public PlaylistListener getPlaylistListener() { 91 | return playlistListener; 92 | } 93 | 94 | public void setPlaylistListener(PlaylistListener playlistListener) { 95 | this.playlistListener = playlistListener; 96 | } 97 | 98 | public TrackListener getPlaylistTrackListener() { 99 | return playlistTrackListener; 100 | } 101 | 102 | public void setPlaylistTrackListener(TrackListener playlistTrackListener) { 103 | this.playlistTrackListener = playlistTrackListener; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/ClipboardUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | 7 | import java.util.Set; 8 | 9 | /** 10 | * Created by theapache64 on 15/12/16. 11 | */ 12 | 13 | public class ClipboardUtils { 14 | 15 | public static final String SOUNDCLOUD_URL_REGEX = ".*(?:http|https):\\/\\/soundcloud\\.com\\/(?:.+)\\/(?:.+).*"; 16 | private static final String X = ClipboardUtils.class.getSimpleName(); 17 | 18 | public static String getSoundCloudUrl(final Context context) { 19 | String clipboardData = null; 20 | 21 | final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 22 | 23 | if (clipboardManager != null) { 24 | 25 | final ClipData primaryClip = clipboardManager.getPrimaryClip(); 26 | 27 | if (primaryClip != null) { 28 | 29 | final ClipData.Item clipData = primaryClip.getItemAt(0); 30 | if (clipData != null) { 31 | clipboardData = clipData.getText().toString(); 32 | } 33 | 34 | if (clipboardData != null) { 35 | 36 | System.out.println("Clipboard: " + clipboardData); 37 | 38 | final Set urls = DownloadIgniter.UrlParser.parseUrls(clipboardData); 39 | if (urls != null) { 40 | for (final String url : urls) { 41 | if (url.matches(SOUNDCLOUD_URL_REGEX)) { 42 | return url; 43 | } 44 | } 45 | } 46 | } 47 | 48 | } 49 | 50 | } 51 | 52 | return null; 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.util.Log; 9 | import android.webkit.MimeTypeMap; 10 | 11 | import java.io.File; 12 | 13 | /** 14 | * Created by theapache64 on 14/12/16. 15 | */ 16 | 17 | public class CommonUtils { 18 | 19 | private static final String X = CommonUtils.class.getSimpleName(); 20 | 21 | static boolean isSupport(final int apiLevel) { 22 | return Build.VERSION.SDK_INT >= apiLevel; 23 | } 24 | 25 | 26 | public static String getMIMETypeFromUrl(final File file, final String defaultValue) { 27 | 28 | MimeTypeMap mime = MimeTypeMap.getSingleton(); 29 | int index = file.getName().lastIndexOf('.') + 1; 30 | String ext = file.getName().substring(index).toLowerCase(); 31 | final String mimeType = mime.getMimeTypeFromExtension(ext); 32 | 33 | if (mimeType != null) { 34 | return mimeType; 35 | } 36 | 37 | return defaultValue; 38 | } 39 | 40 | public static boolean isMyServiceRunning(final Context context, Class serviceClass) { 41 | final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 42 | for (ActivityManager.RunningServiceInfo service : am.getRunningServices(Integer.MAX_VALUE)) { 43 | if (serviceClass.getName().equals(service.service.getClassName())) { 44 | Log.i(X, "Service running : " + serviceClass.getName()); 45 | return true; 46 | } 47 | } 48 | 49 | Log.e(X, "Service not running : " + serviceClass.getName()); 50 | return false; 51 | } 52 | 53 | public static String getSanitizedName(final String fileName) { 54 | return fileName.replaceAll("[^\\w]", "_").replaceAll("[_]{2,}", "_"); 55 | } 56 | 57 | public static void open(Activity activity, File file) { 58 | //Opening audio file 59 | final Intent playIntent = new Intent(Intent.ACTION_VIEW); 60 | playIntent.setDataAndType(UriCompat.fromFile(activity, file), "audio/*"); 61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 62 | playIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 63 | } 64 | activity.startActivity(playIntent); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/DarKnight.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.util.Base64; 4 | import android.util.Log; 5 | 6 | import java.security.Key; 7 | 8 | import javax.crypto.Cipher; 9 | import javax.crypto.spec.SecretKeySpec; 10 | 11 | /* 12 | * Used to simple text enc and dec 13 | * Created by Shifar Shifz on 6/29/2015. 14 | */ 15 | public class DarKnight { 16 | 17 | private static final String ALGORITHM = "AES"; 18 | 19 | private static final byte[] SALT = new byte[]{'t', 'H', 'e', 'A', 'p', 'A', 'c', 'H', 'e', '6', '4', '1', '0', '0', '0', '0'}; 20 | private static final String X = DarKnight.class.getSimpleName(); 21 | 22 | public static String getEncrypted(String plainText) { 23 | 24 | Key salt = getSalt(); 25 | 26 | try { 27 | Cipher cipher = Cipher.getInstance(ALGORITHM); 28 | cipher.init(Cipher.ENCRYPT_MODE, salt); 29 | byte[] encodedValue = cipher.doFinal(plainText.getBytes()); 30 | String encrypted = Base64.encodeToString(encodedValue, Base64.DEFAULT); 31 | Log.d(X, "Encryption Success: " + encrypted); 32 | return encrypted; 33 | } catch (Exception e) { 34 | e.printStackTrace(); 35 | Log.e(X, "Error:" + e.getMessage()); 36 | } 37 | 38 | return null; 39 | } 40 | 41 | public static String getDecrypted(String encodedText) { 42 | 43 | Key salt = getSalt(); 44 | try { 45 | Cipher cipher = Cipher.getInstance(ALGORITHM); 46 | cipher.init(Cipher.DECRYPT_MODE, salt); 47 | byte[] decodedValue = Base64.decode(encodedText, Base64.DEFAULT); 48 | byte[] decValue = cipher.doFinal(decodedValue); 49 | String decrypted = new String(decValue); 50 | Log.d(X, "Decryption Success: " + decrypted); 51 | return decrypted; 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | Log.e(X, "Error:" + e.getMessage()); 55 | } 56 | return null; 57 | } 58 | 59 | public static Key getSalt() { 60 | return new SecretKeySpec(SALT, ALGORITHM); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/DownloadIgniter.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import com.theah64.soundclouddownloader.R; 7 | import com.theah64.soundclouddownloader.database.Tracks; 8 | import com.theah64.soundclouddownloader.services.DownloaderService; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * Created by theapache64 on 15/12/16. 17 | */ 18 | 19 | public class DownloadIgniter { 20 | 21 | public static void ignite(final Context context, String data) { 22 | 23 | final String url = UrlParser.parse(data); 24 | if (url != null && url.contains("soundcloud.com/")) { 25 | 26 | final Intent downloadIntent = new Intent(context, DownloaderService.class); 27 | downloadIntent.putExtra(Tracks.COLUMN_SOUNDCLOUD_URL, url); 28 | context.startService(downloadIntent); 29 | 30 | } else { 31 | //Invalid sound cloud url 32 | SingletonToast.makeText(context, R.string.Invalid_soundcloud_URL); 33 | } 34 | } 35 | 36 | static class UrlParser { 37 | 38 | // Pattern for recognizing a URL, based off RFC 3986 39 | private static final Pattern urlPattern = Pattern.compile( 40 | "(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)" 41 | + "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*" 42 | + "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~@!:/{};']*)", 43 | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); 44 | 45 | 46 | static String parse(final String data) { 47 | 48 | if (data != null) { 49 | Matcher matcher = urlPattern.matcher(data); 50 | if (matcher.find()) { 51 | int matchStart = matcher.start(1); 52 | int matchEnd = matcher.end(); 53 | return data.substring(matchStart, matchEnd); 54 | } 55 | } 56 | 57 | return null; 58 | } 59 | 60 | static Set parseUrls(String data) { 61 | final Matcher matcher = urlPattern.matcher(data); 62 | Set urls = null; 63 | if (matcher.find()) { 64 | urls = new HashSet<>(); 65 | do { 66 | int matchStart = matcher.start(1); 67 | int matchEnd = matcher.end(); 68 | urls.add(data.substring(matchStart, matchEnd)); 69 | } while (matcher.find()); 70 | } 71 | 72 | return urls; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/DownloadUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.app.DownloadManager; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.util.Log; 9 | 10 | import com.theah64.soundclouddownloader.R; 11 | import com.theah64.soundclouddownloader.models.Track; 12 | 13 | import java.io.File; 14 | 15 | /** 16 | * Created by theapache64 on 14/12/16. 17 | */ 18 | public class DownloadUtils { 19 | 20 | private static final String X = DownloadUtils.class.getSimpleName(); 21 | private static final String TEMP_SIGNATURE = ".tmp"; 22 | private final DownloadManager dm; 23 | private final Context context; 24 | 25 | public DownloadUtils(final Context context) { 26 | this.context = context; 27 | this.dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 28 | } 29 | 30 | public static String getSubtitle3(final Track track) { 31 | if (track.isDownloaded() && track.getFile().exists()) { 32 | return "(Saved)"; 33 | } else if (track.isDownloaded() && !track.getFile().exists()) { 34 | return "(Saved but moved/deleted)"; 35 | } else { 36 | return null; 37 | } 38 | } 39 | 40 | public long addToDownloadQueue(final Track track) { 41 | 42 | Log.d(X, "Adding to download queue : " + track.getDownloadUrl() + " track:" + track); 43 | 44 | final DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(track.getDownloadUrl())); 45 | 46 | downloadRequest.setTitle(track.getTitle()); 47 | downloadRequest.setDescription(track.getDownloadUrl()); 48 | 49 | downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); 50 | 51 | final File tempFile = new File(track.getFile().getAbsolutePath() + TEMP_SIGNATURE); 52 | Log.d(X, "Temp file : " + tempFile.getAbsolutePath()); 53 | downloadRequest.setDestinationUri(Uri.fromFile(tempFile)); 54 | return dm.enqueue(downloadRequest); 55 | 56 | } 57 | 58 | public String getVerbalStatus(final Track track) { 59 | 60 | Log.d(X, "Requested for verbal status"); 61 | 62 | 63 | if (track.getDownloadId() != null) { 64 | 65 | DownloadManager.Query query = new DownloadManager.Query(); 66 | query.setFilterById(Long.parseLong(track.getDownloadId())); 67 | 68 | final Cursor cursor = dm.query(query); 69 | if (cursor != null) { 70 | if (cursor.moveToFirst()) { 71 | 72 | final int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); 73 | 74 | switch (downloadStatus) { 75 | 76 | case DownloadManager.STATUS_FAILED: 77 | return context.getString(R.string.Download_failed); 78 | case DownloadManager.STATUS_PAUSED: 79 | return context.getString(R.string.Download_paused); 80 | case DownloadManager.STATUS_PENDING: 81 | return context.getString(R.string.Download_pending); 82 | 83 | case DownloadManager.STATUS_RUNNING: 84 | return context.getString(R.string.Downloading); 85 | 86 | default: 87 | case DownloadManager.STATUS_SUCCESSFUL: 88 | return getSubtitle3(track); 89 | 90 | } 91 | } 92 | cursor.close(); 93 | } 94 | } 95 | 96 | return getSubtitle3(track); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | /** 11 | * Created by shifar on 11/5/16. 12 | */ 13 | public class FileUtils { 14 | /** 15 | * Used to read text assets in the assets directory. 16 | */ 17 | public static String readTextualAsset(final Context context, String filename) throws IOException { 18 | 19 | final InputStream is = context.getAssets().open(filename); 20 | final InputStreamReader isr = new InputStreamReader(is); 21 | final BufferedReader br = new BufferedReader(isr); 22 | final StringBuilder sb = new StringBuilder(); 23 | String line; 24 | while ((line = br.readLine()) != null) { 25 | sb.append(line).append("\n"); 26 | } 27 | is.close(); 28 | br.close(); 29 | isr.close(); 30 | 31 | return sb.toString(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/InputDialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | 4 | import android.annotation.SuppressLint; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.support.annotation.StringRes; 8 | import android.support.v7.app.AlertDialog; 9 | import android.text.InputFilter; 10 | import android.util.Log; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | 14 | import com.theah64.soundclouddownloader.R; 15 | import com.theah64.soundclouddownloader.widgets.AdvancedEditText; 16 | import com.theah64.soundclouddownloader.widgets.ValidTextInputLayout; 17 | 18 | 19 | /** 20 | * Created by shifar on 23/4/16. 21 | */ 22 | public class InputDialogUtils { 23 | 24 | public static final int MAX_LENGTH_INFINITE = -1; 25 | private static final String X = InputDialogUtils.class.getSimpleName(); 26 | private final Context context; 27 | private LayoutInflater inflater; 28 | 29 | public InputDialogUtils(Context context) { 30 | this.context = context; 31 | } 32 | 33 | public void showInputDialog(@StringRes int title, @StringRes int subTitle, @StringRes int placeHolder, final @StringRes int errorMessage, 34 | final BasicInputCallback inputCallback, int maxLength, final String regEx, int inputType, String preText) { 35 | 36 | if (inflater == null) { 37 | inflater = LayoutInflater.from(context); 38 | } 39 | 40 | @SuppressLint("InflateParams") 41 | final View inputLayout = inflater.inflate(R.layout.input_dialog_layout, null); 42 | 43 | //Setting hint 44 | final ValidTextInputLayout vtilInput = (ValidTextInputLayout) inputLayout.findViewById(R.id.vtilInput); 45 | vtilInput.setErrorMessage(errorMessage); 46 | vtilInput.setRegEx(regEx); 47 | vtilInput.setText(preText); 48 | vtilInput.setHint(context.getString(placeHolder)); 49 | 50 | //Setting input length 51 | final AdvancedEditText aetInput = (AdvancedEditText) vtilInput.getEditText(); 52 | 53 | if (aetInput == null) { 54 | throw new IllegalArgumentException("aetInput is null"); 55 | } 56 | 57 | aetInput.setInputType(inputType); 58 | 59 | if (maxLength != MAX_LENGTH_INFINITE) { 60 | aetInput.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)}); 61 | } 62 | 63 | //Creating input dialog 64 | final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context) 65 | .setTitle(title) 66 | .setCancelable(true) 67 | .setMessage(subTitle) 68 | .setView(inputLayout) 69 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 70 | @Override 71 | public void onClick(DialogInterface dialog, int which) { 72 | //to mock the dialog builder, real action below. 73 | } 74 | }) 75 | .setNegativeButton(android.R.string.cancel, null); 76 | 77 | 78 | final AlertDialog dialog = dialogBuilder.create(); 79 | 80 | dialog.show(); 81 | dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() { 82 | @Override 83 | public void onClick(View v) { 84 | 85 | //Validating data here 86 | if (vtilInput.isMatch()) { 87 | 88 | final String inputData = vtilInput.getString().trim(); 89 | 90 | //Informing launched activity 91 | vtilInput.setErrorEnabled(false); 92 | Log.i(X, "Valid data :" + inputData); 93 | inputCallback.onValidInput(inputData); 94 | dialog.dismiss(); 95 | } 96 | 97 | } 98 | }); 99 | 100 | } 101 | 102 | public interface BasicInputCallback { 103 | void onValidInput(final String inputTex); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Created by theapache64 on 11/9/16. 9 | */ 10 | public class NetworkUtils { 11 | 12 | public static boolean hasNetwork(final Context context) { 13 | final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 14 | final NetworkInfo networkInfo = cm.getActiveNetworkInfo(); 15 | return networkInfo != null && networkInfo.isConnected(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/OkHttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import okhttp3.Call; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Response; 11 | 12 | /** 13 | * Created by shifar on 23/7/16. 14 | */ 15 | public class OkHttpUtils { 16 | 17 | public static final String METHOD_GET = "GET"; 18 | 19 | private static final OkHttpClient okHttpClient = new OkHttpClient.Builder() 20 | .connectTimeout(10, TimeUnit.MINUTES) 21 | .readTimeout(10, TimeUnit.MINUTES) 22 | .build(); 23 | 24 | private static final String X = OkHttpUtils.class.getSimpleName(); 25 | private static OkHttpUtils instance = new OkHttpUtils(); 26 | 27 | private OkHttpUtils() { 28 | } 29 | 30 | public static OkHttpUtils getInstance() { 31 | return instance; 32 | } 33 | 34 | public static String logAndGetStringBody(final Response response) throws IOException { 35 | final String stringResp = response.body().string(); 36 | Log.d(X, "JSONResponse : " + stringResp); 37 | return stringResp; 38 | } 39 | 40 | /** 41 | * Used to cancel passed calls. 42 | * 43 | * @param apiCalls 44 | */ 45 | public static void cancelCalls(final Call... apiCalls) { 46 | for (final Call apiCall : apiCalls) { 47 | cancelCall(apiCall); 48 | } 49 | } 50 | 51 | public static void cancelCall(final Call apiCall) { 52 | if (apiCall != null) { 53 | Log.d(X, "Cancelling call"); 54 | Log.d(X, "isCallExecuted : " + apiCall.isExecuted()); 55 | apiCall.cancel(); 56 | } else { 57 | Log.d(X, "API Call is null"); 58 | } 59 | } 60 | 61 | public OkHttpClient getClient() { 62 | return okHttpClient; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/PrefUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | 8 | /** 9 | * Created by shifar on 15/9/16. 10 | */ 11 | public class PrefUtils { 12 | private static final String X = PrefUtils.class.getSimpleName(); 13 | public static final String KEY_IS_START_ON_NEW_TRACK = "is_start_app_on_new_track"; 14 | private static PrefUtils instance; 15 | private final SharedPreferences pref; 16 | 17 | private PrefUtils(final Context context) { 18 | this.pref = PreferenceManager.getDefaultSharedPreferences(context); 19 | } 20 | 21 | public static PrefUtils getInstance(final Context context) { 22 | if (instance == null) { 23 | instance = new PrefUtils(context); 24 | } 25 | return instance; 26 | } 27 | 28 | public SharedPreferences getPref() { 29 | return pref; 30 | } 31 | 32 | public SharedPreferences.Editor getEditor() { 33 | return pref.edit(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/ProfileUtils.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.accounts.Account; 4 | import android.accounts.AccountManager; 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.provider.ContactsContract; 8 | import android.util.Patterns; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Created by theapache64 on 11/9/16. 14 | */ 15 | public class ProfileUtils { 16 | 17 | private final Context context; 18 | private static ProfileUtils instance; 19 | 20 | private ProfileUtils(Context context) { 21 | this.context = context; 22 | } 23 | 24 | public static ProfileUtils getInstance(final Context context) { 25 | if (instance == null) { 26 | instance = new ProfileUtils(context.getApplicationContext()); 27 | } 28 | return instance; 29 | } 30 | 31 | 32 | public String getDeviceOwnerName() { 33 | 34 | if (CommonUtils.isSupport(14)) { 35 | final Cursor c = context.getContentResolver().query(ContactsContract.Profile.CONTENT_URI, null, null, null, null); 36 | if (c != null && c.getCount() > 0 && c.moveToFirst()) { 37 | final String ownerName = c.getString(c.getColumnIndex(ContactsContract.Profile.DISPLAY_NAME)); 38 | c.close(); 39 | return ownerName; 40 | } 41 | 42 | } 43 | 44 | return null; 45 | } 46 | 47 | 48 | public String getPrimaryEmail() { 49 | return getAccountName(Patterns.EMAIL_ADDRESS); 50 | } 51 | 52 | private String getAccountName(final Pattern pattern) { 53 | final Account[] accounts = AccountManager.get(context).getAccounts(); 54 | for (final Account account : accounts) { 55 | if (pattern.matcher(account.name).matches()) { 56 | return account.name; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/Random.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | /** 4 | * Created by theapache64 on 9/12/16. 5 | */ 6 | 7 | public class Random { 8 | private static final int MIN = 0; 9 | private static final int MAX = 1000; 10 | private static final java.util.Random random = new java.util.Random(); 11 | 12 | public static int getRandomInt() { 13 | return random.nextInt(MAX - MIN + 1) + MIN; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/SingletonToast.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.StringRes; 5 | import android.widget.Toast; 6 | 7 | /** 8 | * Created by theapache64 on 29/12/16. 9 | */ 10 | 11 | public class SingletonToast { 12 | 13 | private static Toast toast; 14 | 15 | public static Toast makeText(final Context context, @StringRes final int message) { 16 | return makeText(context, context.getString(message)); 17 | } 18 | 19 | public static Toast makeText(final Context context, final String message) { 20 | if (toast == null) { 21 | toast = Toast.makeText(context, message, Toast.LENGTH_LONG); 22 | } 23 | 24 | toast.setDuration(Toast.LENGTH_LONG); 25 | toast.setText(message); 26 | return toast; 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/utils/UriCompat.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.utils; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.content.FileProvider; 9 | 10 | import com.theah64.soundclouddownloader.BuildConfig; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * Created by theapache64 on 23/1/17. 16 | */ 17 | public class UriCompat { 18 | 19 | public static Uri fromFile(final Context context, File file, @Nullable Intent intent) { 20 | 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 22 | if (intent != null) { 23 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 24 | } 25 | return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); 26 | } 27 | 28 | return Uri.fromFile(file); 29 | } 30 | 31 | public static Uri fromFile(final Context context, File file) { 32 | return fromFile(context, file, null); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/widgets/AdvancedEditText.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.AppCompatEditText; 6 | import android.util.AttributeSet; 7 | 8 | /** 9 | * Created by shifar on 18/1/16. 10 | */ 11 | public class AdvancedEditText extends AppCompatEditText { 12 | 13 | public AdvancedEditText(Context context) { 14 | super(context); 15 | init(); 16 | } 17 | 18 | public AdvancedEditText(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | init(); 21 | } 22 | 23 | public AdvancedEditText(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | init(); 26 | } 27 | 28 | private void init() { 29 | setSingleLine(true); 30 | } 31 | 32 | @Nullable 33 | public String getString() { 34 | final String data = getText().toString().trim(); 35 | if (data.isEmpty()) { 36 | return null; 37 | } 38 | return data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/widgets/ThemedSnackbar.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.widgets; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Build; 6 | import android.support.design.widget.Snackbar; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | import com.theah64.soundclouddownloader.R; 11 | 12 | /** 13 | * Created by theapache64 on 16/12/16. 14 | */ 15 | 16 | public class ThemedSnackbar { 17 | 18 | public static Snackbar make(final Context context, View view, int message, int length) { 19 | 20 | Snackbar snackbar = Snackbar.make(view, message, length); 21 | final View snackLayout = snackbar.getView(); 22 | final TextView textView = (TextView) snackLayout.findViewById(android.support.design.R.id.snackbar_text); 23 | textView.setTextColor(Color.WHITE); 24 | 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 26 | snackbar.setActionTextColor(context.getColor(R.color.deep_orange_500)); 27 | } else { 28 | //noinspection deprecation 29 | snackbar.setActionTextColor(context.getResources().getColor(R.color.deep_orange_500)); 30 | } 31 | 32 | return snackbar; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/app/src/main/java/com/theah64/soundclouddownloader/widgets/ValidTextInputLayout.java: -------------------------------------------------------------------------------- 1 | package com.theah64.soundclouddownloader.widgets; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.StringRes; 5 | import android.support.design.widget.TextInputLayout; 6 | import android.util.AttributeSet; 7 | 8 | import com.theah64.soundclouddownloader.R; 9 | 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Created by theapache64 on 7/2/16. 14 | */ 15 | public final class ValidTextInputLayout extends TextInputLayout { 16 | 17 | private AdvancedEditText et; 18 | private Pattern regExPattern; 19 | private int errorMessage = -1; 20 | 21 | public ValidTextInputLayout(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | private void initEt() { 26 | if (this.et == null) { 27 | this.et = (AdvancedEditText) getEditText(); 28 | if (this.et == null) { 29 | throw new IllegalArgumentException("No EditText child found"); 30 | } 31 | } 32 | } 33 | 34 | public void setRegEx(final String regEx) { 35 | this.regExPattern = Pattern.compile(regEx); 36 | } 37 | 38 | public void setErrorMessage(@StringRes final int errorMessage) { 39 | this.errorMessage = errorMessage; 40 | } 41 | 42 | public boolean isMatch() { 43 | 44 | initEt(); 45 | 46 | if (et != null) { 47 | 48 | final String data = et.getString(); 49 | 50 | if (data != null) { 51 | 52 | if (regExPattern != null) { 53 | 54 | //This will match or nothing 55 | final boolean isMatch = this.regExPattern.matcher(data).matches(); 56 | 57 | if (!isMatch) { 58 | 59 | //Invalid 60 | if (errorMessage != -1) { 61 | setErrorEnabled(true); 62 | setError(getContext().getString(errorMessage)); 63 | } else { 64 | throw new IllegalArgumentException("REGEX is set, but error message is " + errorMessage); 65 | } 66 | 67 | 68 | return false; 69 | } else { 70 | //Valid 71 | setErrorEnabled(false); 72 | return true; 73 | } 74 | } else { 75 | //No regex is not , set it's valid 76 | setErrorEnabled(false); 77 | return true; 78 | } 79 | 80 | } else { 81 | //Data is empty 82 | setErrorEnabled(true); 83 | setError(getContext().getString(R.string.Empty)); 84 | return false; 85 | } 86 | 87 | } else { 88 | throw new IllegalArgumentException("TextInputLayout doesn't has an EditText to validate"); 89 | } 90 | 91 | } 92 | 93 | public void setText(String text) { 94 | initEt(); 95 | et.setText(text); 96 | } 97 | 98 | public String getString() { 99 | initEt(); 100 | return et.getString(); 101 | } 102 | 103 | 104 | /** 105 | * Created by shifar on 7/2/16. 106 | */ 107 | public static final class InputValidator { 108 | 109 | private final ValidTextInputLayout[] validInputLayouts; 110 | 111 | public InputValidator(ValidTextInputLayout... validInputLayouts) { 112 | this.validInputLayouts = validInputLayouts; 113 | } 114 | 115 | 116 | public boolean isAllValid() { 117 | boolean isAllValid = true; 118 | for (final ValidTextInputLayout inputLayout : validInputLayouts) { 119 | isAllValid = inputLayout.isMatch() && isAllValid; 120 | } 121 | return isAllValid; 122 | } 123 | 124 | /** 125 | * Used to clear all field's data. 126 | */ 127 | public void clearFields() { 128 | for (final ValidTextInputLayout vtil : validInputLayouts) { 129 | vtil.setText(null); 130 | } 131 | } 132 | } 133 | 134 | 135 | } -------------------------------------------------------------------------------- /client/app/src/main/res/anim/blink.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi-v11/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi-v11/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi-v9/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi-v9/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_av_album_70dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_av_album_70dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_check_box_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_check_box_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_check_box_outline_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_check_box_outline_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_logo_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_logo_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_logo_no_bg_100dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_logo_no_bg_100dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_more_vert_grey_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_more_vert_grey_18dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_spinner_grey_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_spinner_grey_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_thumb_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_thumb_down_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-hdpi/ic_thumb_up_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-hdpi/ic_thumb_up_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-ldrtl-hdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-ldrtl-hdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-ldrtl-mdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-ldrtl-mdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-ldrtl-xhdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-ldrtl-xhdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-ldrtl-xxhdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-ldrtl-xxhdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi-v11/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi-v11/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi-v9/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi-v9/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_av_album_70dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_av_album_70dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_check_box_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_check_box_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_check_box_outline_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_check_box_outline_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_logo_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_logo_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_logo_no_bg_100dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_logo_no_bg_100dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_more_vert_grey_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_more_vert_grey_18dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_spinner_grey_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_spinner_grey_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_thumb_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_thumb_down_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-mdpi/ic_thumb_up_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-mdpi/ic_thumb_up_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi-v11/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi-v11/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi-v9/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi-v9/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_av_album_70dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_av_album_70dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_check_box_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_check_box_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_check_box_outline_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_check_box_outline_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_logo_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_logo_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_logo_no_bg_100dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_logo_no_bg_100dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_more_vert_grey_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_more_vert_grey_18dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_spinner_grey_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_spinner_grey_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_thumb_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_thumb_down_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xhdpi/ic_thumb_up_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xhdpi/ic_thumb_up_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi-v11/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi-v11/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi-v9/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi-v9/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_arrow_back_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_arrow_back_black_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_av_album_70dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_av_album_70dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_check_box_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_check_box_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_check_box_outline_orange_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_check_box_outline_orange_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_logo_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_logo_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_logo_no_bg_100dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_logo_no_bg_100dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_more_vert_grey_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_more_vert_grey_18dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_spinner_grey_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_spinner_grey_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_stat_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_stat_logo_white.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_thumb_down_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_thumb_down_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable-xxhdpi/ic_thumb_up_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable-xxhdpi/ic_thumb_up_white_24dp.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable/logo_tinypng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theapache64/SoundCloud-Downloader/c82481a20cb5c41b32b4322ba021a5116fe0335a/client/app/src/main/res/drawable/logo_tinypng.png -------------------------------------------------------------------------------- /client/app/src/main/res/drawable/simple_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_downloader.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 29 | 30 | 31 | 32 | 37 | 38 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_playlist_download.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_playlist_tracks.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 24 | 25 | 30 | 31 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_settings_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 20 | 21 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/activity_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/content_playlist_download.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/content_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/content_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/app/src/main/res/layout/fragment_playlists.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 20 | 21 | 27 | 28 |