8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/Filter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public enum Filter {
22 | ARTIST, FOLDER, TREE, GENRE
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RepeatMode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2017 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public enum RepeatMode {
22 | REPEAT_ALL, REPEAT_ONE, REPEAT_GROUP
23 | }
24 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | signingConfigs {
5 | release {
6 | }
7 | }
8 | compileSdkVersion 21
9 | buildToolsVersion '27.0.3'
10 | defaultConfig {
11 | applicationId "souch.smp"
12 | minSdkVersion 8
13 | targetSdkVersion 11
14 | versionCode 16
15 | versionName '1.4'
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | debug {
24 | debuggable true
25 | }
26 | }
27 | productFlavors {
28 | }
29 | }
30 |
31 | dependencies {
32 | compile fileTree(include: ['*.jar'], dir: 'libs')
33 | androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.2.1'
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/fastscroll_thumb_holo.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | 16dp
22 | 16dp
23 |
24 |
--------------------------------------------------------------------------------
/misc/lock.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/misc/artist_ori.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
23 | 64dp
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seekbar_control_selector_holo_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/action_next.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
22 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/action_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
22 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/action_prev.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
22 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/action_goto_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
22 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/souch/smp/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.app.Application;
22 | import android.test.ApplicationTestCase;
23 | import android.test.InstrumentationTestCase;
24 |
25 | /**
26 | * Testing Fundamentals
27 | */
28 | public class ApplicationTest extends ApplicationTestCase {
29 | public ApplicationTest() {
30 | super(Application.class);
31 | }
32 | }
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v11/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/PrefKeys.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public enum PrefKeys {
22 | SONG_ID,
23 | NO_LOCK,
24 | FOLLOW_SONG,
25 | ENABLE_SHAKE,
26 | SHAKE_THRESHOLD,
27 | FILTER,
28 | ROOT_FOLDERS,
29 | DEFAULT_FOLD,
30 | UNFOLD_SUBGROUP,
31 | UNFOLD_SUBGROUP_THRESHOLD,
32 | TEXT_SIZE_NORMAL,
33 | TEXT_SIZE_BIG,
34 | TEXT_SIZE_RATIO,
35 | TEXT_SIZE_CHOOSED,
36 | MEDIA_BUTTON_START_APP,
37 | VIBRATE,
38 | SHUFFLE,
39 | SCROBBLE,
40 | SONG_POS,
41 | SAVE_SONG_POS,
42 | REPEAT_MODE
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seekbar_horizontal_holo_light.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
21 |
22 |
24 |
25 |
26 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RowViewHolder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.view.View;
22 | import android.widget.ImageView;
23 | import android.widget.RelativeLayout;
24 | import android.widget.TextView;
25 |
26 | public class RowViewHolder {
27 | public RowViewHolder(View view) {
28 | layout = (RelativeLayout) view.findViewById(R.id.song_layout);
29 | text = (TextView) view.findViewById(R.id.song_title);
30 | image = (ImageView) view.findViewById(R.id.curr_play);
31 | duration = (TextView) view.findViewById(R.id.song_duration);
32 | }
33 |
34 | public RelativeLayout layout;
35 | public TextView text;
36 | public TextView duration;
37 | public ImageView image;
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_actionbar_launcher_anim.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
22 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
46 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/PlayerState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public class PlayerState {
22 | static public final int Nope = 0b1;
23 | static public final int Idle = 0b10;
24 | static public final int Initialized = 0b100;
25 | static public final int Preparing = 0b1000;
26 | static public final int Prepared = 0b10000;
27 | static public final int Started = 0b100000;
28 | static public final int Paused = 0b1000000;
29 | static public final int PlaybackCompleted = 0b10000000;
30 | static public final int Stopped = 0b100000000;
31 | static public final int End = 0b1000000000;
32 | static public final int Error = 0b10000000000;
33 |
34 | private int state;
35 |
36 | public PlayerState() {
37 | state = Nope;
38 | }
39 |
40 | public void setState(int s) {
41 | state = s;
42 | }
43 |
44 | public int getState() {
45 | return state;
46 | }
47 |
48 | public boolean compare(int states) {
49 | return (state & states) != 0;
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/Parameters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General License for more details.
14 | *
15 | * You should have received a copy of the GNU General License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public interface Parameters {
22 | boolean getNoLock();
23 | void setNoLock(boolean noLock);
24 |
25 | boolean getFollowSong();
26 |
27 | void setChooseTextSize(boolean big);
28 | boolean getChoosedTextSize();
29 | int getBigTextSize();
30 | int getNormalTextSize();
31 | float getTextSizeRatio();
32 |
33 | long getSongID();
34 | void setSongID(long songID);
35 |
36 | boolean getSaveSongPos();
37 |
38 | int getSongPos();
39 | void setSongPos(int songPos);
40 |
41 | Filter getFilter();
42 | void setFilter(Filter filter);
43 |
44 | RepeatMode getRepeatMode();
45 | void setRepeatMode(RepeatMode repeatMode);
46 |
47 | String getRootFolders();
48 |
49 | int getDefaultFold();
50 |
51 | boolean getUnfoldSubGroup();
52 | int getUnfoldSubGroupThreshold();
53 |
54 | boolean getEnableShake();
55 | void setEnableShake(boolean shakeEnabled);
56 |
57 | float getShakeThreshold();
58 |
59 | boolean getMediaButtonStartAppShake();
60 |
61 | boolean getVibrate();
62 |
63 | void setShuffle(boolean shuffle);
64 | boolean getShuffle();
65 |
66 | boolean getScrobble();
67 | }
68 |
--------------------------------------------------------------------------------
/misc/music.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1.8
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/misc/options.svg:
--------------------------------------------------------------------------------
1 |
2 |
72 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RowsAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.Context;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.widget.BaseAdapter;
26 |
27 | public class RowsAdapter extends BaseAdapter {
28 | private Rows rows;
29 | private LayoutInflater songInf;
30 | private Main main;
31 |
32 |
33 | public RowsAdapter(Context c, Rows theRows, Main mn) {
34 | rows = theRows;
35 | songInf = LayoutInflater.from(c);
36 | main = mn;
37 | }
38 |
39 | @Override
40 | public int getCount() {
41 | return rows.size();
42 | }
43 |
44 | @Override
45 | public Object getItem(int arg0) {
46 | // TODO Auto-generated method stub
47 | return null;
48 | }
49 |
50 | @Override
51 | public long getItemId(int arg0) {
52 | // TODO Auto-generated method stub
53 | return 0;
54 | }
55 |
56 |
57 | @Override
58 | public View getView(int position, View convertView, ViewGroup parent) {
59 | View rowView = convertView;
60 | // reuse views
61 | if (rowView == null) {
62 | // map to song layout
63 | rowView = songInf.inflate(R.layout.song, parent, false);
64 | // configure view holder
65 | RowViewHolder viewHolder = new RowViewHolder(rowView);
66 | rowView.setTag(viewHolder);
67 | }
68 |
69 | RowViewHolder holder = (RowViewHolder) rowView.getTag();
70 |
71 | rows.get(position).setView(holder, main, position);
72 |
73 | return rowView;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/misc/edit.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/question.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
70 |
--------------------------------------------------------------------------------
/misc/rescan.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/song.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
25 |
26 |
37 |
43 |
44 |
52 |
53 |
61 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/misc/rewind.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/straight.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/misc/folder.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/text_big.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/skull.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/misc/folded.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/text_regular.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/souch/smp/ParametersStub.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | public class ParametersStub implements Parameters {
22 | public ParametersStub() {}
23 |
24 | public boolean getNoLock() { return true; }
25 | public void setNoLock(boolean noLock) {}
26 |
27 | public boolean getFollowSong() {
28 | return true;
29 | }
30 |
31 | public void setChooseTextSize(boolean big) {}
32 | public boolean getChoosedTextSize() {
33 | return true;
34 | }
35 | public int getBigTextSize() {
36 | return 17;
37 | }
38 | public int getNormalTextSize() {
39 | return 15;
40 | }
41 | public float getTextSizeRatio() {
42 | return 1.1f;
43 | }
44 |
45 |
46 | public long getSongID() {
47 | return -1;
48 | }
49 | public void setSongID(long songID) {}
50 |
51 | public boolean getSaveSongPos() {
52 | return false;
53 | }
54 |
55 | public int getSongPos() {
56 | return -1;
57 | }
58 | public void setSongPos(int songPos) {}
59 |
60 | public Filter getFilter() {
61 | return Filter.FOLDER;
62 | }
63 | public void setFilter(Filter filter) {}
64 |
65 | public RepeatMode getRepeatMode() {
66 | return RepeatMode.REPEAT_ALL;
67 | }
68 | public void setRepeatMode(RepeatMode repeatMode) {}
69 |
70 | public String getRootFolders() {
71 | return "";
72 | }
73 |
74 |
75 | public int getDefaultFold() {
76 | return 0;
77 | }
78 |
79 | public boolean getUnfoldSubGroup() {
80 | return true;
81 | }
82 |
83 | public int getUnfoldSubGroupThreshold() {
84 | return 20;
85 | }
86 |
87 | public boolean getEnableShake() {
88 | return false;
89 | }
90 |
91 | public void setEnableShake(boolean shakeEnabled) {}
92 |
93 | public float getShakeThreshold() {
94 | return 100;
95 | }
96 |
97 | public boolean getMediaButtonStartAppShake() { return true; }
98 |
99 | public boolean getVibrate() { return true; }
100 |
101 | public void setShuffle(boolean shuffle) {};
102 | public boolean getShuffle() { return false; }
103 |
104 | public boolean getScrobble() { return false; }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/Row.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.res.Resources;
22 | import android.util.TypedValue;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | public class Row {
28 | // level from the left
29 | protected int level;
30 | // position of the row within the unfolded rows array
31 | protected int genuinePos;
32 | protected int typeface;
33 | // null if no parent
34 | protected Row parent;
35 |
36 | // must be set outside before calling setText
37 | public static int backgroundColor;
38 | public static int levelOffset;
39 |
40 | public Row(int position, int theLevel, int theTypeface) {
41 | genuinePos = position;
42 | level = theLevel;
43 | typeface = theTypeface;
44 | parent = null;
45 | }
46 |
47 | public void setGenuinePos(int position) { genuinePos = position; }
48 | public int getGenuinePos() { return genuinePos; }
49 |
50 | public Row getParent() { return parent; }
51 | public void setParent(Row parent) { this.parent = parent; }
52 |
53 | public int getLevel() {
54 | return level;
55 | }
56 |
57 | public void setLevel(int level) {
58 | this.level = level;
59 | }
60 |
61 | public void setView(RowViewHolder holder, Main main, int position) {
62 | holder.layout.setBackgroundColor(backgroundColor);
63 | holder.text.setTypeface(null, typeface);
64 | holder.text.setPadding(convertDpToPixels(level * levelOffset, holder.layout.getResources()), 0, 0, 0);
65 | }
66 |
67 | protected String getStringOffset() {
68 | String offset = "", s = " ";
69 | for(int i = level ; i > 0 ; i--) {
70 | offset += s;
71 | }
72 | return offset;
73 | }
74 |
75 | // cache result
76 | private static Map converted = new HashMap<>();
77 | public static int convertDpToPixels(int dp, Resources resources) {
78 | int px;
79 | if (converted.containsKey(dp)) {
80 | px = converted.get(dp);
81 | }
82 | else {
83 | px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,
84 | resources.getDisplayMetrics());
85 | converted.put(dp, px);
86 | }
87 | return px;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/misc/artist.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/misc/loop.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
27 |
28 |
35 |
36 | #f30000
37 |
38 | #fff0f0f0
39 | #ffff0000
40 | #ffffffff
41 | #ffffffff
42 | #88000000
43 | #88353535
44 | #88000000
45 | #88353535
46 | #88000000
47 | #88353535
48 |
53 |
54 |
64 |
65 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/misc/TODO.txt:
--------------------------------------------------------------------------------
1 | # todo
2 |
3 |
4 | put everything related to view into XML
5 | fix lock/unlock not working on some (lots of) devices
6 | fix curr play icon when on last song row
7 | disable bluetooth on call?
8 | volume from app icon
9 | action bar for settings
10 | add slash at the end to folder?
11 | make settings icon fatter
12 | Fix rescan on every android version - on rescan finished, reinitSongs
13 | stop at the end of first stack / go to the next stack (go to beginning if at the end)
14 | adjust brightness when on no lock
15 | show cur song played in total when group folded : i.e. 5/13 (the group has 13 songs and the fifth is selected)
16 | zoom make letter (and button) bigger or smaller
17 | guess "smart" track number (2 < 11, song file name vs id tag, ...)
18 | long press on back button kill the app
19 | option: relock on pause even if unlocked
20 |
21 |
22 | # misc
23 |
24 | 22" at 1600x1200
25 | sqrt(1600*1600+1200*1200)
26 | 2000.00000000000000000000
27 | 2000/22
28 | 90.90909090909090909090 dpi
29 | hdpi:240
30 | xhdp:320
31 |
32 | http://androiddrawables.com/
33 | http://stackoverflow.com/questions/8507726/sending-sms-to-from-a-device-from-to-an-android-virtual-device
34 | # make icon:
35 | http://romannurik.github.io/AndroidAssetStudio/icons-actionbar.html
36 | /opt/android-sdk-linux/platforms/android-10/data/res/
37 | http://android-holo-colors.com/
38 |
39 | # search free icon
40 | https://www.iconfinder.com/icons/103855/speaker_volume_icon#size=128
41 | https://www.iconfinder.com/icons/208018/audio_play_sound_start_video_icon#size=128
42 | https://www.iconfinder.com/icons/111044/closed_lock_secure_icon#size=64
43 | https://www.iconfinder.com/icons/344692/arrow_audio_media_music_outline_play_player_right_sound_video_icon#size=128
44 |
45 | # perf tips
46 | https://developer.android.com/training/articles/perf-tips.html
47 |
48 | # back key
49 | http://android-developers.blogspot.fr/2009/12/back-and-other-hard-keys-three-stories.html
50 |
51 | # listview
52 | http://www.vogella.com/tutorials/AndroidListView/article.html#androidlists
53 | http://udinic.wordpress.com/2011/07/01/selectablelistview-make-selection-work/
54 | https://github.com/beworker/pinned-section-listview
55 |
56 | http://stackoverflow.com/questions/6208315/in-android-how-do-i-query-mediastore-only-for-files-in-a-specific-path-or-alte
57 |
58 | # list source code
59 | /opt/android-sdk-linux/sources/android-21/java/util/ArrayList.java
60 |
61 | # audio focus
62 | http://developer.android.com/guide/topics/media/mediaplayer.html#audiofocus
63 |
64 | # unicode table
65 | http://unicode-table.com/en/#028C
66 |
67 | # zoom
68 | http://android-developers.blogspot.fr/2010/06/making-sense-of-multitouch.html
69 | https://developer.android.com/training/gestures/multi.html
70 |
71 | # get f-droid installations number
72 | wget https://gitlab.com/fdroid/fdroiddata/raw/master/stats/total_downloads_app.txt -O - 2>/dev/null | sed 's/\([^ ]*\) \([^ ]*\)/\2 \1/' | sort -nr | less -N
73 | wget https://gitlab.com/fdroid/fdroiddata/raw/master/stats/total_downloads_app_version.txt -O - 2>/dev/null | grep souch.smp
74 |
75 |
76 | # example metadata f-droid
77 | https://gitlab.com/fdroid/fdroiddata/blob/master/metadata/org.adaway.txt
78 |
79 | setprop persist.sys.language sr;setprop persist.sys.country SR;stop;sleep 5;start
80 | setprop persist.sys.language fr;setprop persist.sys.country FR;stop;sleep 5;start
81 |
82 | # beat detection
83 | https://github.com/ddf/Minim/blob/master/src/ddf/minim/analysis/BeatDetect.java
84 |
85 |
86 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/OnSwipeTouchListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.Context;
22 | import android.view.GestureDetector;
23 | import android.view.GestureDetector.SimpleOnGestureListener;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 | import android.view.View.OnTouchListener;
27 |
28 |
29 | /** thanks to https://stackoverflow.com/questions/4139288/android-how-to-handle-right-to-left-swipe-gestures?rq=1 */
30 | /**
31 | * Detects left and right swipes across a view.
32 | */
33 | public class OnSwipeTouchListener implements OnTouchListener {
34 |
35 | private final GestureDetector gestureDetector;
36 |
37 | public OnSwipeTouchListener(Context context) {
38 | gestureDetector = new GestureDetector(context, new GestureListener());
39 | }
40 |
41 | public void onSwipeLeft() {
42 | }
43 |
44 | public void onSwipeRight() {
45 | }
46 |
47 | public void onSwipeTop() {
48 | }
49 |
50 | public void onSwipeBottom() {
51 | }
52 |
53 | public void performClick() {
54 | }
55 |
56 | public boolean onTouch(View v, MotionEvent event) {
57 | return gestureDetector.onTouchEvent(event);
58 | }
59 |
60 | private final class GestureListener extends SimpleOnGestureListener {
61 |
62 | private static final int SWIPE_DISTANCE_THRESHOLD = 50;
63 | private static final int SWIPE_VELOCITY_THRESHOLD = 50;
64 |
65 | @Override
66 | public boolean onDown(MotionEvent e) {
67 | return true;
68 | }
69 |
70 | @Override
71 | public boolean onSingleTapUp(MotionEvent e) {
72 | performClick();
73 | return true;
74 | }
75 |
76 | @Override
77 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
78 | float distanceX = e2.getX() - e1.getX();
79 | float distanceY = e2.getY() - e1.getY();
80 | if (Math.abs(distanceX) > Math.abs(distanceY) && Math.abs(distanceX) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
81 | if (distanceX > 0)
82 | onSwipeRight();
83 | else
84 | onSwipeLeft();
85 |
86 | return true;
87 | }
88 | else if (Math.abs(distanceY) > Math.abs(distanceX) && Math.abs(distanceY) > SWIPE_DISTANCE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
89 | if (distanceY > 0)
90 | onSwipeBottom();
91 | else
92 | onSwipeTop();
93 |
94 | return true;
95 | }
96 | return false;
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/misc/Hammer_and_sickle.svg:
--------------------------------------------------------------------------------
1 |
2 |
66 |
--------------------------------------------------------------------------------
/misc/unfolded.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/Scrobble.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.Context;
22 | import android.content.Intent;
23 | import android.util.Log;
24 |
25 | public class Scrobble {
26 | private Rows rows;
27 | private Parameters params;
28 | private Context context;
29 |
30 | // from API spcecification
31 | public static final int SCROBBLE_START = 0;
32 | public static final int SCROBBLE_RESUME = 1;
33 | public static final int SCROBBLE_PAUSE = 2;
34 | public static final int SCROBBLE_COMPLETE = 3;
35 |
36 | private boolean started;
37 | private String artist;
38 | private String album;
39 | private String track;
40 | private int duration;
41 |
42 |
43 | public Scrobble(Rows rows, Parameters params, Context ctx) {
44 | this.rows = rows;
45 | this.params = params;
46 | this.context = ctx;
47 |
48 | started = false;
49 | }
50 |
51 | // can be called with SCROBBLE_COMPLETE state twice or more
52 | public void send(int scrobbleState) {
53 | if (!params.getScrobble())
54 | return;
55 |
56 | RowSong rowSong = rows.getCurrSong();
57 | if (rowSong == null) {
58 | Log.w("MusicService", "scrobbleSend exit as rowSong is null!");
59 | return;
60 | }
61 |
62 | if (scrobbleState == SCROBBLE_START) {
63 | started = true;
64 | // save scrobble info for next state
65 | artist = rowSong.getArtist();
66 | album = rowSong.getAlbum();
67 | track = rowSong.getTitle();
68 | duration = rowSong.getDuration();
69 | } else if (scrobbleState == SCROBBLE_COMPLETE) {
70 | // send complete only if SCROBBLE_START was send before
71 | if (!started)
72 | return;
73 |
74 | started = false;
75 | }
76 |
77 | Intent bCast;
78 | // from https://github.com/tgwizard/sls/blob/master/Developer%27s%20API.md
79 | // do not enable Simple Last.FM Droid broadcast as Simple Last.FM Droid handles Scrobble Droid broadcast
80 | /*
81 | bCast = new Intent("com.adam.aslfms.notify.playstatechanged");
82 | bCast.putExtra("state", scrobbleState);
83 | bCast.putExtra("app-name", context.getResources().getString(R.string.app_name));
84 | bCast.putExtra("app-package", context.getPackageName());
85 | bCast.putExtra("artist", artist);
86 | bCast.putExtra("album", album);
87 | bCast.putExtra("track", track);
88 | bCast.putExtra("duration", duration);
89 | bCast.putExtra("source", "P");
90 | context.sendBroadcast(bCast);
91 | */
92 |
93 | // from https://code.google.com/p/scrobbledroid/wiki/DeveloperAPI
94 | bCast = new Intent("net.jjc1138.android.scrobbler.action.MUSIC_STATUS");
95 | bCast.putExtra("playing", scrobbleState == SCROBBLE_START || scrobbleState == SCROBBLE_RESUME);
96 | bCast.putExtra("artist", artist);
97 | bCast.putExtra("album", album);
98 | bCast.putExtra("track", track);
99 | bCast.putExtra("secs", duration);
100 | bCast.putExtra("source", "P");
101 | context.sendBroadcast(bCast);
102 |
103 | Log.d("MusicService", "scrobbleSend " + scrobbleState + " : " + artist + " - " + track);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/misc/loop_group.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Moved to gitlab](https://gitlab.com/souch/SMP)
2 |
3 |
4 | ## SicMu Player for Android
5 |
6 | Every songs of the phone are put in a unique big song list.
7 |
8 | Songs are sorted and grouped by folders, artists, albums and album's track.
9 |
10 | Works on old slow small devices (from Android froyo 2.2).
11 |
12 |
13 | 3.5" screen:
14 |
15 | 
16 | 
17 | 
18 |
19 | [Video](http://youtu.be/LGyjDfwimzA)
20 |
21 | [More screenshots](Screenshots.md)
22 |
23 | ### Installation
24 |
25 | [](https://f-droid.org/repository/browse/?fdid=souch.smp)
26 | [](https://play.google.com/store/apps/details?id=souch.smp)
27 |
28 | ### Detailed features
29 |
30 | - sorted by artists, albums and track number
31 | - or sorted by folder tree, useful for big music list
32 | - or sorted by folders, artists, albums and track number, flattening folder hierarchy
33 | - groups can be folded / unfolded
34 | - repeat mode (all, group, one track)
35 | - shake the phone to go to next song
36 | - show current playing song in the list
37 | - notification when playing
38 | - seek bar
39 | - disable / enable lockscreen
40 | - configurable font size
41 | - on app startup, scroll to last song played
42 | - play mp3, ogg, flac, midi, wav, mp4, 3gp... see android mediaplayer supported media formats (depends on android version).
43 | - bluetooth support (play through bluetooth device)
44 | - media buttons support (next, prev, play/pause) from external device (bluetooth headphones...)
45 | - light and fast: starts in 0.5s and uses 10Mo of RAM with 5Go of music (1000 files, 100 folders) on a 700MHz ARM processor.
46 | - support [Simple Last.fm Scrobbler](https://github.com/tgwizard/sls) or [Scrobble Droid](https://code.google.com/p/scrobbledroid) (disabled by default in settings)
47 |
48 |
49 | ### Help
50 |
51 | - see help section in app's settings
52 |
53 |
54 | ### Todo (perhaps :-)
55 |
56 | - group by genre?
57 | - quick jump with letter in the right (like in contact)?
58 | - search?
59 | - pinned section (upper group level stay at top until another one appears)?
60 | - swipe to go to list of song <-> song details
61 | - mp3 tag editor ?
62 | - playlist?
63 | - audioeffect/equalizer
64 | - turning disc speed follow musics beat
65 |
66 | Detailed todo list available in [TODO.txt](misc/TODO.txt).
67 |
68 |
69 | ### Credits
70 |
71 | Lot's of time saved thanks to Sue Smith's [tutorials on creating a Music Player on Android](http://code.tutsplus.com/tutorials/create-a-music-player-on-android-project-setup--mobile-22764).
72 |
73 | Use some icons done by Daniele De Santis (Creative Commons Attribution 3.0 Unported), found on iconfinder.com.
74 |
75 | Seekbar style created by Jérôme Van Der Linden (Creative Commons Attribution 3.0 Unported), found on http://android-holo-colors.com.
76 |
77 | RepeatingImageButton.java and MediaButtonIntentReceiver.java file mostly copied from official android music player (Apache License, Version 2.0).
78 |
79 |
80 | ### Developer
81 |
82 | Compiled with Android Studio.
83 | Non regression tests are available in the androidTest folder.
84 | Tested on Gingerbread (2.3.6), Nexus 4 JellyBean (4.1.2), Samsung S3 (4.3), Nexus 5 Lollipop (5.0).
85 |
86 | Feel free to add GitHub issues (feature request, bugs...).
87 | If you need a feature that is in the todolist, open a feature request on github to speed up its development.
88 |
89 | A UML class diagram describe a bit the architecture for people that would want to develop the software [UmlClass.png](misc/UmlClass.png).
90 |
91 | SicMu player pronounced zicmu. S is for Super Simple Sorted Souch player.
92 |
93 |
94 | ### Donation
95 |
96 | If you don't know what to do with your money or want to make me smile the whole day:
97 | [donate](http://rodolphe.souchaud.free.fr/donate)
98 |
99 |
100 | ### License
101 |
102 | SicMu Player is licensed under the GPLv3. See file [LICENSE](LICENSE) for more details.
103 |
104 |
--------------------------------------------------------------------------------
/misc/shake.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/souch/smp/PathTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.test.AndroidTestCase;
22 | import android.util.Log;
23 |
24 | import java.util.ArrayList;
25 |
26 | public class PathTest extends AndroidTestCase {
27 |
28 | public void setUp() throws Exception {
29 | super.setUp();
30 | Log.d("PathTest", "====================================");
31 | }
32 |
33 | public void testGetFolderEmpty() throws Exception {
34 | tryGetFolder("", ".");
35 | }
36 |
37 | public void testGetFolderUsual() throws Exception {
38 | Path.rootFolders = "/mnt/sdcard";
39 | tryGetFolder("/mnt/sdcard/toto/tata.mp3", "toto");
40 | }
41 |
42 | public void testGetFolderUsual2() throws Exception {
43 | Path.rootFolders = "/mnt/sdcard";
44 | tryGetFolder("/mnt/sdcard/toto/titi/tata.mp3", "toto/titi");
45 | }
46 |
47 | public void testGetFolderUsualSlashRootFolder() throws Exception {
48 | Path.rootFolders = "/mnt/sdcard/";
49 | tryGetFolder("/mnt/sdcard/toto/titi/tata.mp3", "toto/titi");
50 | }
51 |
52 | public void testGetFolderAllRootFolder() throws Exception {
53 | Path.rootFolders = "/mnt/sdcard";
54 | tryGetFolder("/mnt/sdcard/tata.mp3", ".");
55 |
56 | Path.rootFolders = "/mnt/sdcard/";
57 | tryGetFolder("/mnt/sdcard/tata.mp3", ".");
58 | }
59 |
60 | public void testGetFolderMangleRootFolder() throws Exception {
61 | Path.rootFolders = "/mnt/sdcard";
62 | tryGetFolder("/mnt/sdcard.mp3", ".");
63 |
64 | Path.rootFolders = "/mnt/sdcard/";
65 | tryGetFolder("/mnt/sdcard.mp3", "/mnt");
66 | }
67 |
68 | private void tryGetFolder(String path, String expectedFolder) throws Exception {
69 | String folder = Path.getFolder(path);
70 | if (!folder.equals(expectedFolder)) {
71 | String msg = "Expected '" + expectedFolder + "' got: '" + folder + "'";
72 | Log.d("PathTest", msg);
73 | throw new Exception(msg);
74 | }
75 | }
76 |
77 |
78 | public void testTokenizeFolderUsual() throws Exception {
79 | tryTokenizeFolder("/mnt/sdcard/toto", new String[]{"mnt", "sdcard", "toto"});
80 | tryTokenizeFolder("/mnt/sdcard/toto/", new String[]{"mnt", "sdcard", "toto"});
81 | tryTokenizeFolder("/mnt/sdcard/toto/o", new String[]{"mnt", "sdcard", "toto", "o"});
82 | }
83 |
84 | public void testTokenizeFolderOne() throws Exception {
85 | tryTokenizeFolder("/mnt/", new String[]{"mnt"});
86 | tryTokenizeFolder("/mnt", new String[]{"mnt"});
87 | }
88 |
89 | public void testTokenizeFolderStrange() throws Exception {
90 | tryTokenizeFolder("/./", new String[]{"."});
91 | tryTokenizeFolder(".", new String[]{"."});
92 | tryTokenizeFolder("/toot///yo", new String[]{"toot", "yo"});
93 | tryTokenizeFolder("/toot///", new String[]{"toot"});
94 | tryTokenizeFolder("//", new String[]{});
95 | tryTokenizeFolder("/", new String[]{});
96 | tryTokenizeFolder("", new String[]{});
97 | }
98 |
99 | private void tryTokenizeFolder(String folder, String[] expectedFolders) throws Exception {
100 | ArrayList folders = Path.tokenizeFolder(folder);
101 | if (folders.size() != expectedFolders.length) {
102 | String msg = "Expected folder length'" + expectedFolders.length + "' got: '" + folders.size() + "'";
103 | Log.d("PathTest", msg);
104 | throw new Exception(msg);
105 | }
106 | for(int i = 0; i < folders.size(); i++) {
107 | if (!folders.get(i).equals(expectedFolders[i])) {
108 | String msg = "Expected folder " + expectedFolders[i] + "' got: '" + folders.get(i) + "'";
109 | Log.d("PathTest", msg);
110 | throw new Exception(msg);
111 | }
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RepeatingImageButton.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // This file has been copied from official android music player (Apache License, Version 2.0).
18 | package souch.smp;
19 |
20 | import android.content.Context;
21 | import android.os.SystemClock;
22 | import android.util.AttributeSet;
23 | import android.view.KeyEvent;
24 | import android.view.MotionEvent;
25 | import android.view.View;
26 | import android.widget.ImageButton;
27 |
28 | /**
29 | * A button that will repeatedly call a 'listener' method
30 | * as long as the button is pressed.
31 | */
32 | public class RepeatingImageButton extends ImageButton {
33 |
34 | private long mStartTime;
35 | private int mRepeatCount;
36 | private RepeatListener mListener;
37 | private long mInterval = 500;
38 |
39 | public RepeatingImageButton(Context context) {
40 | this(context, null);
41 | }
42 |
43 | public RepeatingImageButton(Context context, AttributeSet attrs) {
44 | this(context, attrs, android.R.attr.imageButtonStyle);
45 | }
46 |
47 | public RepeatingImageButton(Context context, AttributeSet attrs, int defStyle) {
48 | super(context, attrs, defStyle);
49 | setFocusable(true);
50 | setLongClickable(true);
51 | }
52 |
53 | /**
54 | * Sets the listener to be called while the button is pressed and
55 | * the interval in milliseconds with which it will be called.
56 | * @param l The listener that will be called
57 | * @param interval The interval in milliseconds for calls
58 | */
59 | public void setRepeatListener(RepeatListener l, long interval) {
60 | mListener = l;
61 | mInterval = interval;
62 | }
63 |
64 | @Override
65 | public boolean performLongClick() {
66 | mStartTime = SystemClock.elapsedRealtime();
67 | mRepeatCount = 0;
68 | post(mRepeater);
69 | return true;
70 | }
71 |
72 | @Override
73 | public boolean onTouchEvent(MotionEvent event) {
74 | if (event.getAction() == MotionEvent.ACTION_UP) {
75 | // remove the repeater, but call the hook one more time
76 | removeCallbacks(mRepeater);
77 | if (mStartTime != 0) {
78 | doRepeat(true);
79 | mStartTime = 0;
80 | }
81 | }
82 | return super.onTouchEvent(event);
83 | }
84 |
85 | @Override
86 | public boolean onKeyDown(int keyCode, KeyEvent event) {
87 | switch (keyCode) {
88 | case KeyEvent.KEYCODE_DPAD_CENTER:
89 | case KeyEvent.KEYCODE_ENTER:
90 | // need to call super to make long press work, but return
91 | // true so that the application doesn't get the down event.
92 | super.onKeyDown(keyCode, event);
93 | return true;
94 | }
95 | return super.onKeyDown(keyCode, event);
96 | }
97 |
98 | @Override
99 | public boolean onKeyUp(int keyCode, KeyEvent event) {
100 | switch (keyCode) {
101 | case KeyEvent.KEYCODE_DPAD_CENTER:
102 | case KeyEvent.KEYCODE_ENTER:
103 | // remove the repeater, but call the hook one more time
104 | removeCallbacks(mRepeater);
105 | if (mStartTime != 0) {
106 | doRepeat(true);
107 | mStartTime = 0;
108 | }
109 | }
110 | return super.onKeyUp(keyCode, event);
111 | }
112 |
113 | private Runnable mRepeater = new Runnable() {
114 | public void run() {
115 | doRepeat(false);
116 | if (isPressed()) {
117 | postDelayed(this, mInterval);
118 | }
119 | }
120 | };
121 |
122 | private void doRepeat(boolean last) {
123 | long now = SystemClock.elapsedRealtime();
124 | if (mListener != null) {
125 | mListener.onRepeat(this, now - mStartTime, last ? -1 : mRepeatCount++);
126 | }
127 | }
128 |
129 | public interface RepeatListener {
130 | /**
131 | * This method will be called repeatedly at roughly the interval
132 | * specified in setRepeatListener(), for as long as the button
133 | * is pressed.
134 | * @param v The button as a View.
135 | * @param duration The number of milliseconds the button has been pressed so far.
136 | * @param repeatcount The number of previous calls in this sequence.
137 | * If this is going to be the last call in this sequence (i.e. the user
138 | * just stopped pressing the button), the value will be -1.
139 | */
140 | void onRepeat(View v, long duration, int repeatcount);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RowGroup.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.graphics.Typeface;
22 | import android.util.TypedValue;
23 | import android.widget.TextView;
24 |
25 | public class RowGroup extends Row {
26 | protected String name;
27 | protected boolean folded;
28 | protected boolean selected;
29 | private int color;
30 | private int nbRowSong;
31 | public static Filter rowType;
32 | protected static int textSize = 18;
33 |
34 | // must be set outside before calling setText
35 | public static int normalTextColor;
36 | public static int playingTextColor;
37 |
38 | public RowGroup(int pos, int level, String name, int typeface, int color) {
39 | super(pos, level, typeface);
40 | this.name = name;
41 | folded = false;
42 | selected = false;
43 | this.color = color;
44 | }
45 |
46 | public String getName() { return name; }
47 |
48 | public boolean isFolded() { return folded; }
49 | public void setFolded(boolean fold) { folded = fold; }
50 |
51 | public void setSelected(boolean selected) { this.selected = selected; }
52 | public boolean isSelected() { return selected; }
53 |
54 | // get number of songs (excluding RowGroup) inside this group
55 | public int nbRowSong() { return nbRowSong; }
56 | public void incNbRowSong() { nbRowSong++; }
57 |
58 | public void setView(RowViewHolder holder, Main main, int position) {
59 | super.setView(holder, main, position);
60 |
61 | float factor = 1.5f;
62 | if (main.getMusicSrv().getRows().isLastRow(position))
63 | factor = 3f;
64 | holder.layout.getLayoutParams().height = convertDpToPixels((int) (textSize * factor),
65 | holder.layout.getResources());
66 |
67 | setText(holder.text);
68 | setDuration(holder.duration);
69 | holder.image.setImageResource(android.R.color.transparent);
70 |
71 | holder.layout.setBackgroundColor(color);
72 | }
73 |
74 | private void setText(TextView text) {
75 | String prefix = "";
76 | if (rowType == Filter.TREE) {
77 | if (isFolded())
78 | prefix = "| ";
79 | else
80 | prefix = "\\ ";
81 |
82 | }
83 | text.setText(prefix + name);
84 |
85 | if (isFolded() && isSelected())
86 | text.setTextColor(playingTextColor);
87 | else
88 | text.setTextColor(normalTextColor);
89 |
90 | text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
91 | //text.setPaintFlags(text.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
92 | //text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
93 | }
94 |
95 | private void setDuration(TextView duration) {
96 | String rightSpace = getStringOffset();
97 | //super.setText(text);
98 | if (isFolded()) {
99 | if (isSelected())
100 | duration.setTextColor(playingTextColor);
101 | else
102 | duration.setTextColor(normalTextColor);
103 | duration.setText(nbRowSong + " |" + rightSpace);
104 | }
105 | else {
106 | duration.setText("/" + rightSpace);
107 | duration.setTextColor(normalTextColor);
108 | }
109 |
110 | duration.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
111 | duration.setTypeface(null, typeface == Typeface.ITALIC ? Typeface.NORMAL : typeface);
112 | /*
113 | duration.setBackgroundColor(Color.argb(0x88, 0x30, 0x30, 0x30));
114 | duration.setId(position);
115 | duration.setOnClickListener(new View.OnClickListener() {
116 | public void onClick(View durationView) {
117 | Log.d("Main", "durationView.getId(): " + durationView.getId());
118 | durationView.setBackgroundColor(Color.argb(0x88, 0x65, 0x65, 0x65));
119 |
120 | class InvertFold implements Runnable {
121 | View view;
122 | InvertFold(View view) { this.view = view; }
123 | public void run() {
124 | main.invertFold(view.getId());
125 | // todo: reset highlight color for a few ms after invertFold?
126 | }
127 | }
128 | durationView.postDelayed(new InvertFold(durationView), 200);
129 | }
130 | });
131 | */
132 | }
133 |
134 |
135 | public String toString() {
136 | return "Group pos: " + genuinePos + " level: " + level + " name: " + name;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/ParametersImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.Context;
22 | import android.content.SharedPreferences;
23 | import android.preference.PreferenceManager;
24 |
25 | public class ParametersImpl implements Parameters {
26 | private Context context;
27 | public ParametersImpl(Context context) {
28 | this.context = context;
29 | }
30 |
31 | private SharedPreferences getPref() {
32 | return PreferenceManager.getDefaultSharedPreferences(context);
33 | }
34 | private SharedPreferences.Editor getEditor() {
35 | return getPref().edit();
36 | }
37 |
38 | public boolean getNoLock() {
39 | return getPref().getBoolean(PrefKeys.NO_LOCK.name(), false);
40 | }
41 | public void setNoLock(boolean noLock) {
42 | getEditor().putBoolean(PrefKeys.NO_LOCK.name(), noLock).commit();
43 | }
44 |
45 | public boolean getFollowSong() {
46 | return getPref().getBoolean(PrefKeys.FOLLOW_SONG.name(), true);
47 | }
48 |
49 | public void setChooseTextSize(boolean big) {
50 | getEditor().putBoolean(PrefKeys.TEXT_SIZE_CHOOSED.name(), big).commit();
51 | }
52 | public boolean getChoosedTextSize() {
53 | return getPref().getBoolean(PrefKeys.TEXT_SIZE_CHOOSED.name(),
54 | Boolean.valueOf(context.getString(R.string.settings_text_size_choosed_default)));
55 | }
56 | public int getBigTextSize() {
57 | return Integer.valueOf(getPref().getString(PrefKeys.TEXT_SIZE_BIG.name(),
58 | context.getString(R.string.settings_text_size_big_default)));
59 | }
60 | public int getNormalTextSize() {
61 | return Integer.valueOf(getPref().getString(PrefKeys.TEXT_SIZE_NORMAL.name(),
62 | context.getString(R.string.settings_text_size_regular_default)));
63 | }
64 | public float getTextSizeRatio() {
65 | return Float.valueOf(getPref().getString(PrefKeys.TEXT_SIZE_RATIO.name(),
66 | context.getString(R.string.settings_text_size_ratio_default)));
67 | }
68 |
69 |
70 | public long getSongID() {
71 | return getPref().getLong(PrefKeys.SONG_ID.name(), -1);
72 | }
73 | public void setSongID(long songID) {
74 | getEditor().putLong(PrefKeys.SONG_ID.name(), songID).commit();
75 | }
76 |
77 | public boolean getSaveSongPos() {
78 | return getPref().getBoolean(PrefKeys.SAVE_SONG_POS.name(), false);
79 | }
80 |
81 | public int getSongPos() {
82 | return getPref().getInt(PrefKeys.SONG_POS.name(), -1);
83 | }
84 | public void setSongPos(int songPos) {
85 | getEditor().putInt(PrefKeys.SONG_POS.name(), songPos).commit();
86 | }
87 |
88 |
89 | public Filter getFilter() {
90 | return Filter.valueOf(getPref().getString(PrefKeys.FILTER.name(), Filter.TREE.name()));
91 | }
92 | public void setFilter(Filter filter) {
93 | getEditor().putString(PrefKeys.FILTER.name(), filter.name()).commit();
94 | }
95 |
96 | public RepeatMode getRepeatMode() {
97 | return RepeatMode.valueOf(getPref().getString(PrefKeys.REPEAT_MODE.name(), RepeatMode.REPEAT_ALL.name()));
98 | }
99 | public void setRepeatMode(RepeatMode repeatMode) {
100 | getEditor().putString(PrefKeys.REPEAT_MODE.name(), repeatMode.name()).commit();
101 | }
102 |
103 | public String getRootFolders() {
104 | return getPref().getString(PrefKeys.ROOT_FOLDERS.name(), Path.getMusicStoragesStr(context));
105 | }
106 |
107 |
108 | public int getDefaultFold() {
109 | return Integer.valueOf(getPref().getString(PrefKeys.DEFAULT_FOLD.name(), "0"));
110 | }
111 |
112 | public boolean getUnfoldSubGroup() {
113 | return getPref().getBoolean(PrefKeys.UNFOLD_SUBGROUP.name(), false);
114 | }
115 |
116 | public int getUnfoldSubGroupThreshold() {
117 | return Integer.valueOf(getPref().getString(PrefKeys.UNFOLD_SUBGROUP_THRESHOLD.name(),
118 | context.getString(R.string.settings_unfold_subgroup_threshold_default)));
119 | }
120 |
121 | public boolean getEnableShake() {
122 | return getPref().getBoolean(PrefKeys.ENABLE_SHAKE.name(), false);
123 | }
124 |
125 | public void setEnableShake(boolean shakeEnabled) {
126 | getEditor().putBoolean(PrefKeys.ENABLE_SHAKE.name(), shakeEnabled).commit();
127 | }
128 |
129 | public float getShakeThreshold() {
130 | return Float.valueOf(getPref().getString(PrefKeys.SHAKE_THRESHOLD.name(),
131 | context.getString(R.string.settings_default_shake_threshold)));
132 | }
133 |
134 | public boolean getMediaButtonStartAppShake() {
135 | return getPref().getBoolean(PrefKeys.MEDIA_BUTTON_START_APP.name(), true);
136 | }
137 |
138 | public boolean getVibrate() {
139 | return getPref().getBoolean(PrefKeys.VIBRATE.name(), true);
140 | }
141 |
142 | public boolean getShuffle() {
143 | return getPref().getBoolean(PrefKeys.SHUFFLE.name(), false);
144 | }
145 |
146 | public void setShuffle(boolean shuffle) {
147 | getEditor().putBoolean(PrefKeys.SHUFFLE.name(), shuffle).commit();
148 | }
149 |
150 | public boolean getScrobble() {
151 | return getPref().getBoolean(PrefKeys.SCROBBLE.name(), false);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/MediaButtonIntentReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | // This file has been heavily copied from official android music player (Apache License, Version 2.0).
19 | // modified to remove shuffle mode
20 | package souch.smp;
21 |
22 |
23 | import android.content.BroadcastReceiver;
24 | import android.content.Context;
25 | import android.content.Intent;
26 | import android.media.AudioManager;
27 | import android.os.Handler;
28 | import android.os.Message;
29 | import android.view.KeyEvent;
30 |
31 | public class MediaButtonIntentReceiver extends BroadcastReceiver {
32 |
33 | // private static final int MSG_LONGPRESS_TIMEOUT = 1;
34 | // private static final int LONG_PRESS_DELAY = 1000;
35 |
36 | private static long mLastClickTime = 0;
37 | // private static boolean mDown = false;
38 |
39 | // souch: disable shuffle mode support
40 | /*
41 | private static boolean mLaunched = false;
42 |
43 | private static Handler mHandler = new Handler() {
44 | @Override
45 | public void handleMessage(Message msg) {
46 | switch (msg.what) {
47 | case MSG_LONGPRESS_TIMEOUT:
48 | if (!mLaunched) {
49 | Context context = (Context)msg.obj;
50 | Intent i = new Intent();
51 | i.putExtra("autoshuffle", "true");
52 | i.setClass(context, MusicBrowserActivity.class);
53 | i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
54 | context.startActivity(i);
55 | mLaunched = true;
56 | }
57 | break;
58 | }
59 | }
60 | };
61 | */
62 |
63 | @Override
64 | public void onReceive(Context context, Intent intent) {
65 | String intentAction = intent.getAction();
66 | if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intentAction)) {
67 | Intent i = new Intent(context, MusicService.class);
68 | i.setAction(MusicService.SERVICECMD);
69 | i.putExtra(MusicService.CMDNAME, MusicService.CMDPAUSE);
70 | context.startService(i);
71 | } else if (Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) {
72 | KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
73 |
74 | if (event == null) {
75 | return;
76 | }
77 |
78 | int keycode = event.getKeyCode();
79 | int action = event.getAction();
80 | long eventtime = event.getEventTime();
81 |
82 | // single quick press: pause/resume.
83 | // double press: next track
84 | // long press: start auto-shuffle mode.
85 |
86 | String command = null;
87 | switch (keycode) {
88 | case KeyEvent.KEYCODE_MEDIA_STOP:
89 | command = MusicService.CMDSTOP;
90 | break;
91 | case KeyEvent.KEYCODE_HEADSETHOOK:
92 | case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
93 | command = MusicService.CMDTOGGLEPAUSE;
94 | break;
95 | case KeyEvent.KEYCODE_MEDIA_NEXT:
96 | command = MusicService.CMDNEXT;
97 | break;
98 | case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
99 | command = MusicService.CMDPREVIOUS;
100 | break;
101 | case KeyEvent.KEYCODE_MEDIA_PAUSE:
102 | command = MusicService.CMDPAUSE;
103 | break;
104 | case KeyEvent.KEYCODE_MEDIA_PLAY:
105 | command = MusicService.CMDPLAY;
106 | break;
107 | }
108 |
109 | if (command != null) {
110 | if (action == KeyEvent.ACTION_DOWN) {
111 | /*if (mDown) {
112 | if ((MusicService.CMDTOGGLEPAUSE.equals(command) ||
113 | MusicService.CMDPLAY.equals(command))
114 | && mLastClickTime != 0
115 | && eventtime - mLastClickTime > LONG_PRESS_DELAY) {
116 | mHandler.sendMessage(mHandler.obtainMessage(MSG_LONGPRESS_TIMEOUT, context));
117 | }
118 | }
119 | else */
120 | if (event.getRepeatCount() == 0) {
121 | // only consider the first event in a sequence, not the repeat events,
122 | // so that we don't trigger in cases where the first event went to
123 | // a different app (e.g. when the user ends a phone call by
124 | // long pressing the headset button)
125 |
126 | // The service may or may not be running, but we need to send it
127 | // a command.
128 | Intent i = new Intent(context, MusicService.class);
129 | i.setAction(MusicService.SERVICECMD);
130 |
131 | if (keycode == KeyEvent.KEYCODE_HEADSETHOOK && eventtime - mLastClickTime < 300) {
132 | i.putExtra(MusicService.CMDNAME, MusicService.CMDNEXT);
133 | context.startService(i);
134 | mLastClickTime = 0;
135 | } else {
136 | i.putExtra(MusicService.CMDNAME, command);
137 | context.startService(i);
138 | mLastClickTime = eventtime;
139 | }
140 |
141 | //mLaunched = false;
142 | //mDown = true;
143 | }
144 | } /* else {
145 | //mHandler.removeMessages(MSG_LONGPRESS_TIMEOUT);
146 | mDown = false;
147 | }
148 | if (isOrderedBroadcast()) {
149 | abortBroadcast();
150 | }
151 | */
152 | }
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
55 |
56 |
62 |
63 |
69 |
70 |
76 |
77 |
81 |
82 |
83 |
84 |
85 |
86 |
91 |
92 |
97 |
98 |
103 |
104 |
109 |
110 |
114 |
115 |
120 |
121 |
126 |
127 |
128 |
129 |
132 |
133 |
136 |
137 |
140 |
141 |
144 |
145 |
149 |
150 |
151 |
152 |
153 |
154 |
157 |
158 |
159 |
162 |
163 |
164 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/app/src/main/java/souch/smp/RowSong.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SicMu Player - Lightweight music player for Android
3 | * Copyright (C) 2015 Mathieu Souchaud
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package souch.smp;
20 |
21 | import android.content.ContentUris;
22 | import android.content.Context;
23 | import android.graphics.Bitmap;
24 | import android.graphics.BitmapFactory;
25 | import android.graphics.Typeface;
26 | import android.net.Uri;
27 | import android.provider.MediaStore;
28 | import android.util.TypedValue;
29 | import android.widget.ImageView;
30 | import android.widget.TextView;
31 | import android.database.Cursor;
32 |
33 | import java.io.File;
34 |
35 | public class RowSong extends Row {
36 | private long id;
37 | private long albumId;
38 | private String title;
39 | private String artist;
40 | private String album;
41 | private int duration;
42 | private int track;
43 | private int year;
44 | // full filename
45 | private String path;
46 | // folder of the path (i.e. last folder containing the file's song)
47 | private String folder;
48 |
49 | protected static int textSize = 15;
50 |
51 | // must be set outside before calling setText
52 | public static int normalSongTextColor;
53 | public static int normalSongDurationTextColor;
54 |
55 | public RowSong(int pos, int level, long songID, String songTitle, String songArtist, String songAlbum,
56 | int dur, int songTrack, String songPath, long albumId, int year) {
57 | super(pos, level, Typeface.NORMAL);
58 | id = songID;
59 | title = songTitle;
60 | artist = songArtist;
61 | album = songAlbum;
62 | duration = dur;
63 | track = songTrack;
64 | path = songPath;
65 | this.albumId = albumId;
66 | this.year = year;
67 | if(path != null) {
68 | folder = Path.getFolder(path);
69 | }
70 | }
71 |
72 | public long getID(){return id;}
73 | public String getTitle(){return title;}
74 | public int getYear(){return year;}
75 | public String getArtist(){return artist;}
76 | public String getAlbum(){return album;}
77 | public int getDuration(){return duration;}
78 | public int getTrack(){return track;}
79 | public String getPath(){return path;}
80 | public String getFolder(){return folder;}
81 | public long getAlbumId(){return albumId;}
82 |
83 |
84 | public void setView(RowViewHolder holder, Main main, int position) {
85 | super.setView(holder, main, position);
86 |
87 | float factor = 1.5f;
88 | if (main.getMusicSrv().getRows().isLastRow(position))
89 | factor = 3f;
90 | holder.layout.getLayoutParams().height = convertDpToPixels((int) (textSize * factor),
91 | holder.layout.getResources());
92 |
93 | setText(holder.text);
94 | setDuration(holder.duration);
95 | setCurrIcon(holder.image, main);
96 | }
97 |
98 | private void setText(TextView text) {
99 | text.setText(title);
100 | text.setTextColor(normalSongTextColor);
101 | text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
102 | }
103 |
104 | private void setDuration(TextView duration) {
105 | duration.setText(secondsToMinutes(getDuration()) + getStringOffset());
106 | duration.setTextColor(normalSongDurationTextColor);
107 | duration.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
108 | duration.setTypeface(null, typeface);
109 | /*
110 | duration.setBackgroundColor(Color.argb(0x00, 0x0, 0x0, 0x0));
111 | duration.setOnClickListener(null);
112 | */
113 | }
114 |
115 | private void setCurrIcon(ImageView img, Main main) {
116 | int currIcon = android.R.color.transparent;
117 | if (this == main.getMusicSrv().getRows().getCurrSong()) {
118 | if (main.getMusicSrv().playingLaunched())
119 | currIcon = R.drawable.ic_curr_play;
120 | else
121 | currIcon = R.drawable.ic_curr_pause;
122 | }
123 | img.setImageResource(currIcon);
124 | // useful only for the tests
125 | img.setTag(currIcon);
126 | }
127 |
128 | public String toString() {
129 | return "Song pos: " + genuinePos + " level: " + level + " ID: " + id + " artist: " + artist +
130 | " album: " + album + " title: " + title + " " +
131 | secondsToMinutes(duration) + " track:" + track + " path: " + path;
132 | }
133 |
134 | static public String secondsToMinutes(int duration){
135 | long seconds = duration;
136 | long minutes = seconds / 60;
137 | seconds = seconds % 60;
138 | return String.valueOf(minutes) + (seconds < 10 ? ":0" : ":") + String.valueOf(seconds);
139 | }
140 |
141 |
142 | public boolean delete(Context context) {
143 | if ((new File(path)).delete()) {
144 | // delete it from media store too
145 | Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
146 | context.getContentResolver().delete(uri, null, null);
147 |
148 | return true;
149 | }
150 | return false;
151 | }
152 |
153 | public Bitmap getAlbumBmp(Context context) {
154 | return getAlbumBmp(context, 0);
155 | }
156 |
157 | /** if imageNum > 0: try to get Nth bitmap from same folder
158 | * @return null if bitmap not found
159 | */
160 | public Bitmap getAlbumBmp(Context context, int imageNum) {
161 | Bitmap bmp = null;
162 | try {
163 | if (imageNum == 0) {
164 | // search using media store
165 | Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
166 | new String[]{MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ALBUM_ART},
167 | MediaStore.Audio.Albums._ID + "=?",
168 | new String[]{String.valueOf(albumId)},
169 | null);
170 | if (cursor.moveToFirst()) {
171 | String path = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Albums.ALBUM_ART));
172 | bmp = BitmapFactory.decodeFile(path);
173 | }
174 | }
175 |
176 | // search manually in song's path
177 | if (bmp == null && path != null) {
178 | File dir = new File(path).getParentFile();
179 | if (dir.exists() && dir.isDirectory()) {
180 | File[] files = dir.listFiles();
181 | if (files != null)
182 | for (File file : files) {
183 | if (Path.isImage(file)) {
184 | if (imageNum == 0) {
185 | // found
186 | bmp = BitmapFactory.decodeFile(file.getAbsolutePath());
187 | break;
188 | }
189 | imageNum--;
190 | }
191 |
192 | }
193 | }
194 | }
195 | }
196 | catch(Exception e) {
197 | bmp = null;
198 | }
199 | return bmp;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/app/src/main/res/values-sr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | ЗикаМу плејер
22 |
23 | Поставке
24 | Протрес. иск.
25 | Протрес. укљ.
26 |
27 | Поређај по фасцикли
28 | Поређај по извођачу
29 |
30 | Пуштање
31 |
32 | Не могу да привилегујем звучни излаз! Поново покрените апликацију!
33 |
34 | Поставке
35 |
36 | Сажми
37 | Рашири
38 |
39 | Приказ песама
40 | Стање фасцикли по покретању
41 |
42 | Сажете
43 | Раширене
44 |
45 | Рашири подгрупе
46 | Омогућено: раширивање групе раширује све подгрупе и песме.
47 | Рашири подгрупе ако група
48 | има мање од %d песама
49 |
50 |
51 | Фонт приказа песама
52 | Изаберите величину фонта
53 | Нормални фонт
54 | Велики фонт
55 | Однос величине текста извођач/песма
56 |
57 |
58 | Поново скенирај
59 | Поновна претрага медија фајлова.
60 | Андроид Киткет онемогућио поновно скенирање.
61 | Скенирање медија окинуто...
62 |
63 | Медија тастери аутоматски покрећу
64 | Медија тастери покрећу апликацију ако већ није покренута.
65 |
66 | Клизај на нову пуштену песму
67 | Клизај на песму када почне пуштање
68 |
69 | Вибрирај на додир
70 | Завибрирај на кратко по додиру на дугмад или списак песама.
71 |
72 | Корена фасцикла
73 | Путања %s не постоји!
74 |
75 | Протресање
76 | Протресање пушта следећу песму.
77 | Праг силе протресања
78 | Ваш уређај нема акцелерометар. Ова функција је онемогућена.
79 |
80 |
81 | Помоћ
82 | Ако сте мало изгубљени.
83 |
84 | Пуштање песме
85 | Тапните на песму да бисте је пустили.
86 | Пуштање песме из групе
87 | Тапните и држите групу да бисте пустили прву песму.
88 | Сажимање/раширивање групе
89 | Тапните групу да бисте је сажели/раширили.
90 | Клизање на текућу песму
91 | Притисните дугме десно од дугмета „следећа“ да бисте клизали на песму која се пушта.
92 | Онемогућавање закључавања екрана
93 | Притисните дугме-катанац да бисте онемогућили екран закључавања (спречава закључавање само кад је апликација у првом плану; не ради на неким уређајима).
94 | Корена фасцикла
95 | Можете да прилагодите корену фасциклу: користи се само за скривање првог дела путање фасцикли када су песме поређане по фасцикли.
96 | Позадина
97 | У току пуштања музике притисак на почетни тастер или тастер за назад ће да затвори списак песама и настави да пушта музику у позадини.
98 | Напуштање
99 | Апликација ће да изађе по притиску тастера за назад ако музика није пуштена. Ако не, Андроид ће аутоматски да је затвори ако зафали меморије.
100 | Заустављање
101 | Апликација ће остати у меморији по притиску на почетни тастер ако музика није пуштена, али Андроид ће аутоматски да је затвори ако зафали меморије.
102 | Медија тастери
103 | Медија тастери (нпр. на блутут слушалицама) могу да контролишу апликацију (следећа, претходна, пауза/пуштање).
104 | Медија тастери аутоматски покрећу
105 | Ако је апликација затворена, медија тастер ће да је покрене. Да бисте ово онемогућили искључите опцију „Медија тастери аутоматски покрећу“.
106 |
107 |
108 | Разно
109 |
110 | О програму
111 | Подаци о апликацији и аутору.
112 |
113 | Издање
114 | Аутор
115 | Mathieu Souchaud
116 | Изворни кôд
117 | Лиценца
118 | ГПЛв3
119 | Донација
120 | Кликните овде ако би вам живот био тежак без ове апликације.
121 |
122 | Посебне захвале
123 | Иконе
124 | Daniele De Santis & WPZOOM (иконе са iconfinder.com)
125 | Упутства
126 | Sue Smith (упутства за прављење музичког плејера за Андроид)
127 | Превод
128 | Младен Пејаковић (српски), Mehmet Gülmen (Türkçe), Jerônimo Cordoni Pellegrini (Português)
129 |
130 |
131 |
--------------------------------------------------------------------------------