├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_info_black_24dp.png │ │ │ │ ├── ic_sync_black_24dp.png │ │ │ │ ├── ic_settings_white_24dp.png │ │ │ │ └── ic_equalizer_white_24dp.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_info_black_24dp.png │ │ │ │ ├── ic_sync_black_24dp.png │ │ │ │ ├── ic_settings_white_24dp.png │ │ │ │ └── ic_equalizer_white_24dp.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_info_black_24dp.png │ │ │ │ ├── ic_sync_black_24dp.png │ │ │ │ ├── ic_equalizer_white_24dp.png │ │ │ │ └── ic_settings_white_24dp.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_info_black_24dp.png │ │ │ │ ├── ic_sync_black_24dp.png │ │ │ │ ├── ic_settings_white_24dp.png │ │ │ │ └── ic_equalizer_white_24dp.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_info_black_24dp.png │ │ │ │ ├── ic_sync_black_24dp.png │ │ │ │ ├── ic_equalizer_white_24dp.png │ │ │ │ └── ic_settings_white_24dp.png │ │ │ ├── layout │ │ │ │ ├── spinner_item.xml │ │ │ │ ├── spinner_dropdown_item.xml │ │ │ │ ├── equalizer_channel.xml │ │ │ │ ├── activity_equalizer.xml │ │ │ │ ├── content_main.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── control_channel.xml │ │ │ │ ├── equalizer_row.xml │ │ │ │ └── control_row.xml │ │ │ ├── xml │ │ │ │ ├── pref_about.xml │ │ │ │ ├── pref_headers.xml │ │ │ │ └── pref_connection.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── drawable-v21 │ │ │ │ ├── ic_equalizer_white_24dp.xml │ │ │ │ ├── ic_info_black_24dp.xml │ │ │ │ ├── ic_sync_black_24dp.xml │ │ │ │ └── ic_settings_white_24dp.xml │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── menu │ │ │ │ ├── menu_equalizer.xml │ │ │ │ └── menu_main.xml │ │ │ ├── layout-land │ │ │ │ ├── equalizer_channel.xml │ │ │ │ ├── activity_equalizer.xml │ │ │ │ └── equalizer_row.xml │ │ │ ├── values-cs │ │ │ │ └── strings.xml │ │ │ └── values-nl │ │ │ │ └── strings.xml │ │ ├── java │ │ │ └── cz │ │ │ │ └── jiriskorpil │ │ │ │ └── amixerwebui │ │ │ │ ├── control │ │ │ │ ├── IBooleanControl.java │ │ │ │ ├── InterfaceType.java │ │ │ │ ├── IEnumeratedControl.java │ │ │ │ ├── ControlType.java │ │ │ │ ├── IControl.java │ │ │ │ ├── IChannel.java │ │ │ │ ├── IIntegerControl.java │ │ │ │ ├── mixer │ │ │ │ │ ├── MixerControl.java │ │ │ │ │ ├── BooleanControl.java │ │ │ │ │ ├── Channel.java │ │ │ │ │ ├── MixerControlFactory.java │ │ │ │ │ ├── EnumeratedControl.java │ │ │ │ │ └── IntegerControl.java │ │ │ │ ├── ControlContainerType.java │ │ │ │ ├── ControlFactory.java │ │ │ │ ├── Control.java │ │ │ │ ├── Equalizer.java │ │ │ │ ├── ControlParser.java │ │ │ │ ├── ControlContainer.java │ │ │ │ ├── EqualizerAdapter.java │ │ │ │ └── ControlContainerAdapter.java │ │ │ │ ├── task │ │ │ │ ├── RetrieveCardHttpRequestTask.java │ │ │ │ ├── ChangeSourceHttpRequestTask.java │ │ │ │ ├── RetrieveHostnameHttpRequestTask.java │ │ │ │ ├── ToggleControlHttpRequestTask.java │ │ │ │ ├── RetrieveEqualizerHttpRequestTask.java │ │ │ │ ├── RetrieveCardsHttpRequestTask.java │ │ │ │ ├── ChangeVolumeHttpRequestTask.java │ │ │ │ ├── RetrieveControlsHttpRequestTask.java │ │ │ │ ├── ChangeCardHttpRequestTask.java │ │ │ │ ├── ChangeEqualizerHttpRequestTask.java │ │ │ │ └── AsyncHttpRequestTask.java │ │ │ │ └── activity │ │ │ │ ├── AppCompatPreferenceActivity.java │ │ │ │ ├── EqualizerActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── SettingsActivity.java │ │ │ │ └── DataHandler.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── cz │ │ │ └── jiriskorpil │ │ │ └── amixerwebui │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── cz │ │ └── jiriskorpil │ │ └── amixerwebui │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── screenshot.png ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── LICENSE ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/screenshot.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-hdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-hdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-mdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-mdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_info_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_sync_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_equalizer_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-hdpi/ic_equalizer_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_equalizer_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-mdpi/ic_equalizer_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xhdpi/ic_equalizer_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxhdpi/ic_equalizer_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_equalizer_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_equalizer_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JiriSko/amixer-webui-android/HEAD/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri May 04 17:37:41 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/spinner_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #009688 4 | #00796A 5 | #80CBC4 6 | #009688 7 | #006064 8 | #F5F5F5 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_equalizer_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/cz/jiriskorpil/amixerwebui/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void additionIsCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cz/jiriskorpil/amixerwebui/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | 4dp 8 | 8dp 9 | 8dp 10 | 4dp 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_equalizer.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/IBooleanControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface for representing boolean control (on/off) 5 | */ 6 | public interface IBooleanControl extends IControl 7 | { 8 | /** 9 | * Checks whether control is active. 10 | * 11 | * @return true if control is active 12 | */ 13 | boolean isChecked(); 14 | 15 | /** 16 | * Sets control value. 17 | * 18 | * @param value true if control should be active 19 | */ 20 | void setChecked(boolean value); 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/spinner_dropdown_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_sync_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/equalizer_channel.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/InterfaceType.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface types. 5 | */ 6 | public enum InterfaceType 7 | { 8 | MIXER, 9 | PCM, 10 | UNKNOWN; 11 | 12 | /** 13 | * Converts string to interface type. 14 | * 15 | * @param iface interface type as string 16 | * @return interface type 17 | */ 18 | public static InterfaceType fromString(String iface) 19 | { 20 | switch (iface) 21 | { 22 | case "MIXER": 23 | return MIXER; 24 | case "PCM": 25 | return PCM; 26 | default: 27 | return UNKNOWN; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_headers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
9 | 10 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/IEnumeratedControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface for representing enumerated control (select one from many). 5 | */ 6 | public interface IEnumeratedControl extends IControl 7 | { 8 | /** 9 | * Returns array of items names. 10 | * 11 | * @return array of names 12 | */ 13 | String[] getItems(); 14 | 15 | /** 16 | * Returns index of currently active item. 17 | * 18 | * @return active item index 19 | */ 20 | int getValue(); 21 | 22 | /** 23 | * Sets index of active item. 24 | * 25 | * @param index active item index 26 | */ 27 | void setValue(int index); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlType.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Control types. 5 | */ 6 | public enum ControlType 7 | { 8 | ENUMERATED, 9 | BOOLEAN, 10 | INTEGER, 11 | UNKNOWN; 12 | 13 | /** 14 | * Converts string to control type. 15 | * 16 | * @param type control type as string 17 | * @return control type 18 | */ 19 | public static ControlType fromString(String type) 20 | { 21 | switch (type) 22 | { 23 | case "ENUMERATED": 24 | return ENUMERATED; 25 | case "BOOLEAN": 26 | return BOOLEAN; 27 | case "INTEGER": 28 | return INTEGER; 29 | default: 30 | return UNKNOWN; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/IControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface for representing base control. 5 | */ 6 | public interface IControl 7 | { 8 | /** 9 | * Returns control identifier. 10 | * 11 | * @return identifier 12 | */ 13 | int getId(); 14 | 15 | /** 16 | * Returns interface type. 17 | * 18 | * @return type of interface 19 | */ 20 | InterfaceType getIface(); 21 | 22 | /** 23 | * Returns access string for control. 24 | * 25 | * @return access string 26 | */ 27 | String getAccess(); 28 | 29 | /** 30 | * Returns control name. 31 | * 32 | * @return name 33 | */ 34 | String getName(); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/IChannel.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface for representing one channel in integer control. 5 | */ 6 | public interface IChannel 7 | { 8 | /** 9 | * Returns parent integer control. 10 | * 11 | * @return parent control 12 | */ 13 | IIntegerControl getControl(); 14 | 15 | /** 16 | * Returns channel name. 17 | * 18 | * @return name 19 | */ 20 | String getName(); 21 | 22 | /** 23 | * Returns channel value. 24 | * 25 | * @return value 26 | */ 27 | int getValue(); 28 | 29 | /** 30 | * Sets channel value. 31 | * 32 | * @param value channel value 33 | */ 34 | void setValue(int value); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/RetrieveCardHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for retrieving active sound card from server. 7 | */ 8 | public class RetrieveCardHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public RetrieveCardHttpRequestTask(Context context, String url, OnFinishListener listener) 11 | { 12 | super(context, url); 13 | this.mOnFinishListener = listener; 14 | } 15 | 16 | /** 17 | * {@inheritDoc} 18 | * Retrieves active sound card from server. 19 | */ 20 | @Override 21 | protected String doInBackground(String... params) 22 | { 23 | return downloadFromURL("/card/"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 C:\Users\jiris\AppData\Local\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 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/IIntegerControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Interface for representing integer control (common volume slider). 5 | */ 6 | public interface IIntegerControl extends IControl 7 | { 8 | /** 9 | * Returns array of volume channels. 10 | * 11 | * @return array of volume channels 12 | */ 13 | IChannel[] getChannels(); 14 | 15 | /** 16 | * Returns volume slider maximum. 17 | * 18 | * @return slider minimum 19 | */ 20 | int getMin(); 21 | 22 | /** 23 | * Returns volume slider maximum value. 24 | * 25 | * @return slider maximum 26 | */ 27 | int getMax(); 28 | 29 | /** 30 | * Returns volume slider step. 31 | * 32 | * @return slider step 33 | */ 34 | int getStep(); 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/ChangeSourceHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for sending request to change source of specific control. 7 | */ 8 | public class ChangeSourceHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public ChangeSourceHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | /** 16 | * {@inheritDoc} 17 | * Sends request to change source. 18 | * 19 | * @param params first parameter is control id, 20 | * second parameter is new source 21 | */ 22 | @Override 23 | protected String doInBackground(String... params) 24 | { 25 | return downloadFromURL("/source/" + params[0] + "/" + params[1] + "/", "PUT"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/RetrieveHostnameHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for retrieving server's hostname. 7 | */ 8 | public class RetrieveHostnameHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public RetrieveHostnameHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | public RetrieveHostnameHttpRequestTask(Context context, String url, OnFinishListener listener) 16 | { 17 | this(context, url); 18 | this.mOnFinishListener = listener; 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | * Retrieves server's hostname. 24 | */ 25 | @Override 26 | protected String doInBackground(String... params) 27 | { 28 | return downloadFromURL("/hostname/"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/ToggleControlHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for sending request to turn on/off specific control. 7 | */ 8 | public class ToggleControlHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public ToggleControlHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | /** 16 | * {@inheritDoc} 17 | * Sends request to turn on/off. 18 | * 19 | * @param params first parameter is control id, 20 | * second parameter is value (0 to turn off, 1 to turn on) 21 | */ 22 | @Override 23 | protected String doInBackground(String... params) 24 | { 25 | return downloadFromURL("/control/" + params[0] + "/" + params[1] + "/", "PUT"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/RetrieveEqualizerHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for retrieving equalizer from server. 7 | */ 8 | public class RetrieveEqualizerHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public RetrieveEqualizerHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | public RetrieveEqualizerHttpRequestTask(Context context, String url, OnFinishListener listener) 16 | { 17 | this(context, url); 18 | this.mOnFinishListener = listener; 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | * Retrieves controls from server. 24 | */ 25 | @Override 26 | protected String doInBackground(String... params) 27 | { 28 | return downloadFromURL("/equalizer/"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/RetrieveCardsHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for retrieving available sound cards from server. 7 | */ 8 | public class RetrieveCardsHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public RetrieveCardsHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | public RetrieveCardsHttpRequestTask(Context context, String url, OnFinishListener listener) 16 | { 17 | this(context, url); 18 | this.mOnFinishListener = listener; 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | * Retrieves available sound cards from server. 24 | */ 25 | @Override 26 | protected String doInBackground(String... params) 27 | { 28 | return downloadFromURL("/cards/"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/ChangeVolumeHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for sending request to change volumes of specific control. 7 | */ 8 | public class ChangeVolumeHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public ChangeVolumeHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | /** 16 | * {@inheritDoc} 17 | * Sends request to change volumes. 18 | * 19 | * @param params first parameters is control id, 20 | * second parameter is new set of volumes [in form: (/)*, e.g. 50/40/60 ] 21 | */ 22 | @Override 23 | protected String doInBackground(String... params) 24 | { 25 | return downloadFromURL("/volume/" + params[0] + "/" + params[1] + "/", "PUT"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/RetrieveControlsHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for retrieving controls from server while user is waiting on progress dialog. 7 | */ 8 | public class RetrieveControlsHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public RetrieveControlsHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | public RetrieveControlsHttpRequestTask(Context context, String url, OnFinishListener listener) 16 | { 17 | this(context, url); 18 | this.mOnFinishListener = listener; 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | * Retrieves controls from server. 24 | */ 25 | @Override 26 | protected String doInBackground(String... params) 27 | { 28 | return downloadFromURL("/controls/"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/ChangeCardHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for sending request to change sound card. 7 | */ 8 | public class ChangeCardHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public ChangeCardHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | public ChangeCardHttpRequestTask(Context context, String url, OnFinishListener listener) 16 | { 17 | this(context, url); 18 | this.mOnFinishListener = listener; 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | * Sends request to change card. 24 | * 25 | * @param params first parameter is card id 26 | */ 27 | @Override 28 | protected String doInBackground(String... params) 29 | { 30 | return downloadFromURL("/card/" + params[0] + "/", "PUT"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/ChangeEqualizerHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Asynchronous task for sending request to change equalizer values of specific control. 7 | */ 8 | public class ChangeEqualizerHttpRequestTask extends AsyncHttpRequestTask 9 | { 10 | public ChangeEqualizerHttpRequestTask(Context context, String url) 11 | { 12 | super(context, url); 13 | } 14 | 15 | /** 16 | * {@inheritDoc} 17 | * Sends request to change equalizer values. 18 | * 19 | * @param params first parameters is control id, 20 | * second parameter is new set of volumes [in form: (/)*, e.g. 50/40/60 ] 21 | */ 22 | @Override 23 | protected String doInBackground(String... params) 24 | { 25 | return downloadFromURL("/equalizer/" + params[0] + "/" + params[1] + "/", "PUT"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/res/layout-land/equalizer_channel.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_equalizer.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_equalizer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/MixerControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import cz.jiriskorpil.amixerwebui.control.Control; 7 | import cz.jiriskorpil.amixerwebui.control.ControlType; 8 | 9 | /** 10 | * Represents mixer control. 11 | */ 12 | public class MixerControl extends Control 13 | { 14 | private ControlType type; 15 | 16 | /** 17 | * Creates new instance of mixer control. 18 | * 19 | * @param jsonObject JSON object representing integer control retrieved from server 20 | * @throws JSONException 21 | */ 22 | public MixerControl(JSONObject jsonObject) throws JSONException 23 | { 24 | super(jsonObject); 25 | 26 | type = ControlType.fromString(jsonObject.getString("type")); 27 | } 28 | 29 | /** 30 | * Returns mixer control type. 31 | * 32 | * @return type of mixer control 33 | */ 34 | public ControlType getType() 35 | { 36 | return type; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 22 | 23 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlContainerType.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Control container types. 5 | */ 6 | public enum ControlContainerType 7 | { 8 | SOURCE, 9 | SWITCH, 10 | VOLUME, 11 | UNKNOWN; 12 | 13 | /** 14 | * Converts control name to control container type. 15 | * 16 | * @param name control as string 17 | * @return control container type 18 | */ 19 | public static ControlContainerType fromName(String name) 20 | { 21 | if (name.matches("^(.*) Source$")) { 22 | return SOURCE; 23 | } else if (name.matches("^(.*) Switch")) { 24 | return SWITCH; 25 | } else if (name.matches("^(.*) Volume$")) { 26 | return VOLUME; 27 | } 28 | return UNKNOWN; 29 | } 30 | 31 | /** 32 | * Returns container name based on control name. 33 | * 34 | * @param name control name 35 | * @return container name 36 | */ 37 | public static String getContainerName(String name) 38 | { 39 | return name.replaceAll("^(.*) (Source|Switch|Volume)$", "$1"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/equalizer_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/BooleanControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import cz.jiriskorpil.amixerwebui.control.IBooleanControl; 7 | 8 | /** 9 | * Represents boolean control (on/off). 10 | */ 11 | public class BooleanControl extends MixerControl implements IBooleanControl 12 | { 13 | private boolean value; 14 | 15 | /** 16 | * Creates new instance of boolean control. 17 | * 18 | * @param jsonObject JSON object representing boolean control retrieved from server 19 | * @throws JSONException 20 | */ 21 | public BooleanControl(JSONObject jsonObject) throws JSONException 22 | { 23 | super(jsonObject); 24 | value = (boolean) jsonObject.getJSONArray("values").get(0); 25 | } 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | public boolean isChecked() 31 | { 32 | return value; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public void setChecked(boolean value) 39 | { 40 | this.value = value; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlFactory.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import android.util.Log; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | import cz.jiriskorpil.amixerwebui.control.mixer.MixerControlFactory; 9 | 10 | /** 11 | * Factory for create new control from JSON object 12 | */ 13 | @SuppressWarnings("PMD.ClassNamingConventions") 14 | public final class ControlFactory 15 | { 16 | private ControlFactory() {} 17 | 18 | /** 19 | * Creates control instance. 20 | * 21 | * @param jsonObject JSON object representing control retrieved from server 22 | * @return new control instance 23 | */ 24 | public static Control createControl(JSONObject jsonObject) 25 | { 26 | try 27 | { 28 | switch (InterfaceType.fromString(jsonObject.getString("iface"))) 29 | { 30 | case MIXER: 31 | return MixerControlFactory.createMixerControl(jsonObject); 32 | default: 33 | return new Control(jsonObject); 34 | } 35 | } catch (JSONException e) { 36 | Log.e("JSONException", "Error: " + e.toString()); 37 | } 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/control_channel.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 24 | 25 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jiří Škorpil 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | buildToolsVersion "27.0.3" 6 | defaultConfig { 7 | applicationId "cz.jiriskorpil.amixerwebui" 8 | minSdkVersion 15 9 | targetSdkVersion 27 10 | versionCode 8 11 | versionName "0.3.2" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | ext.supportLibraryVersion = '27.1.1' 22 | dependencies { 23 | implementation fileTree(include: ['*.jar'], dir: 'libs') 24 | testImplementation 'junit:junit:4.12' 25 | implementation "com.android.support:appcompat-v7:$supportLibraryVersion" 26 | implementation "com.android.support:design:$supportLibraryVersion" 27 | implementation "com.android.support:support-v4:$supportLibraryVersion" 28 | implementation "com.android.support:cardview-v7:$supportLibraryVersion" 29 | implementation "com.android.support:recyclerview-v7:$supportLibraryVersion" 30 | implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:0.7.0' 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_settings_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/equalizer_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ALSA Mixer WebUI for Android 2 | 3 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) [![Translate - with Stringlate](https://img.shields.io/badge/translate%20with-stringlate-green.svg)](https://lonamiwebs.github.io/stringlate/translate?git=https%3A%2F%2Fgithub.com%2FJiriSko%2Famixer-webui-android) [![Codeac](https://static.codeac.io/badges/2-71501879.svg "Codeac")](https://app.codeac.io/github/JiriSko/amixer-webui-android) 4 | 5 | Android client for control ALSA volume on remote server using [ALSA Mixer WebUI](https://github.com/JiriSko/amixer-webui/). 6 | 7 | 8 | Get it on F-Droid 9 | 10 | [![Screenshot](screenshot.png)](screenshot.png) 11 | 12 | ## Supported version 13 | 14 | - Android 4.0.3 and newer 15 | 16 | ## Usage 17 | 18 | - Install from [latest APK](https://github.com/JiriSko/amixer-webui-android/releases/download/v0.3.2/amixer-webui-v0.3.2.apk), or 19 | - Get it on [F-Droid](https://f-droid.org/packages/cz.jiriskorpil.amixerwebui/) 20 | - Build from source code 21 | 22 | ## License 23 | 24 | The application is released under [The MIT License](LICENSE). 25 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/Channel.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import cz.jiriskorpil.amixerwebui.control.IChannel; 4 | import cz.jiriskorpil.amixerwebui.control.IIntegerControl; 5 | 6 | /** 7 | * Represents one channel in integer control. 8 | */ 9 | public class Channel implements IChannel 10 | { 11 | private IIntegerControl control; 12 | private String name; 13 | private int value; 14 | 15 | /** 16 | * Creates new channel instance. 17 | * 18 | * @param control parent control 19 | * @param name channel name 20 | * @param value channel value 21 | */ 22 | public Channel(IIntegerControl control, String name, int value) 23 | { 24 | this.control = control; 25 | this.name = name; 26 | this.value = value; 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public IIntegerControl getControl() 33 | { 34 | return control; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | public String getName() 41 | { 42 | return name; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | public int getValue() 49 | { 50 | return value; 51 | } 52 | 53 | /** 54 | * {@inheritDoc} 55 | */ 56 | public void setValue(int value) 57 | { 58 | this.value = value; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/MixerControlFactory.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import android.util.Log; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | import cz.jiriskorpil.amixerwebui.control.ControlType; 9 | 10 | /** 11 | * Factory for create new mixer control from JSON object 12 | */ 13 | @SuppressWarnings("PMD.ClassNamingConventions") 14 | public final class MixerControlFactory 15 | { 16 | private MixerControlFactory() {} 17 | 18 | /** 19 | * Creates mixer control instance. 20 | * 21 | * @param jsonObject JSON object representing mixer control retrieved from server 22 | * @return new mixer control instance 23 | */ 24 | @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") 25 | public static MixerControl createMixerControl(JSONObject jsonObject) 26 | { 27 | try { 28 | switch (ControlType.fromString(jsonObject.getString("type"))) 29 | { 30 | case ENUMERATED: 31 | return new EnumeratedControl(jsonObject); 32 | case BOOLEAN: 33 | return new BooleanControl(jsonObject); 34 | case INTEGER: 35 | return new IntegerControl(jsonObject); 36 | } 37 | } catch (JSONException e) { 38 | Log.e("JSONException", "Error: " + e.toString()); 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/Control.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * Represents one control. 8 | */ 9 | public class Control implements IControl 10 | { 11 | private int id; 12 | private InterfaceType iface; 13 | private String access; 14 | private String name; 15 | 16 | /** 17 | * Creates new instance of control. 18 | * 19 | * @param jsonObject JSON object representing control retrieved from server 20 | * @throws JSONException 21 | */ 22 | public Control(JSONObject jsonObject) throws JSONException 23 | { 24 | super(); 25 | id = jsonObject.getInt("id"); 26 | iface = InterfaceType.fromString(jsonObject.getString("iface")); 27 | access = jsonObject.getString("access"); 28 | name = jsonObject.getString("name"); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public int getId() 35 | { 36 | return id; 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public InterfaceType getIface() 43 | { 44 | return iface; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public String getAccess() 51 | { 52 | return access; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | public String getName() 59 | { 60 | return name; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/EnumeratedControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import cz.jiriskorpil.amixerwebui.control.IEnumeratedControl; 7 | 8 | /** 9 | * Represents enumerated control (select one from many). 10 | */ 11 | public class EnumeratedControl extends MixerControl implements IEnumeratedControl 12 | { 13 | private String[] items; 14 | private int value; 15 | 16 | /** 17 | * Creates new instance of enumerated control. 18 | * 19 | * @param jsonObject JSON object representing enumerated control retrieved from server 20 | * @throws JSONException 21 | */ 22 | public EnumeratedControl(JSONObject jsonObject) throws JSONException 23 | { 24 | super(jsonObject); 25 | 26 | value = (int) jsonObject.getJSONArray("values").get(0); 27 | 28 | items = new String[jsonObject.getJSONObject("items").length()]; 29 | for (int i = 0; i < items.length; i++) { 30 | items[i] = jsonObject.getJSONObject("items").getString(String.valueOf(i)); 31 | } 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public String[] getItems() 38 | { 39 | return items; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public int getValue() 46 | { 47 | return value; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | public void setValue(int value) 54 | { 55 | this.value = value; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/res/values-cs/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ALSA Mixer WebUI 3 | Nastavení 4 | Nastavení 5 | Jiří Škorpil 6 | 7 | Stahování přerušeno 8 | Spojení selhalo! 9 | Opakovat 10 | 11 | Uzamknout posuvníky 12 | Kanály jsou uzamčeny. 13 | Kanály jsou odemčeny. 14 | %1$d %% 15 | 16 | Zvuková karta 17 | Ekvalizér 18 | 19 | 20 | 21 | 22 | 23 | Připojení 24 | 25 | IP adresa 26 | 192.168.1.5 27 | 28 | Port 29 | 8080 30 | 31 | 32 | 33 | O aplikaci 34 | 35 | Autor 36 | Verze 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ALSA Mixer WebUI 3 | Settings 4 | Settings 5 | Jiří Škorpil 6 | 7 | Downloading canceled 8 | Connection failed! 9 | RETRY 10 | 11 | Lock sliders 12 | Channel sliders are locked together. 13 | Channel sliders are unlocked. 14 | %1$d %% 15 | 16 | Sound card 17 | Equalizer 18 | 19 | 20 | 21 | 22 | 23 | Connection 24 | 25 | IP address 26 | 192.168.1.5 27 | 28 | Port 29 | 8080 30 | 31 | 32 | 33 | About 34 | 35 | Author 36 | Version 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ALSA Mixer WebUI 3 | Instellingen 4 | Instellingen 5 | Jiří Škorpil 6 | 7 | Downloaden geannuleerd 8 | Verbinding mislukt! 9 | Opnieuw proberen 10 | 11 | Vergrendel schuifregelaars 12 | Kanaal schuifregelaars zijn samen vergrendeld 13 | Kanaal schuifregelaars zijn ontgrendeld 14 | %1$d %% 15 | 16 | Geluidskaart 17 | Niveauregelaar 18 | 19 | 20 | 21 | 22 | 23 | Connectie 24 | 25 | IP adres 26 | 192.168.1.5 27 | 28 | Poort 29 | 8080 30 | 31 | 32 | 33 | Over 34 | 35 | Auteur 36 | Versie 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/Equalizer.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import android.util.Log; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class Equalizer implements Parcelable 14 | { 15 | private List controls; 16 | private String rawData; 17 | 18 | public Equalizer(String rawData) 19 | { 20 | controls = new ArrayList<>(); 21 | this.rawData = rawData; 22 | } 23 | 24 | public List getControls() 25 | { 26 | if (controls.isEmpty()) 27 | { 28 | try { 29 | controls = ControlParser.parse(new JSONArray(rawData)); 30 | } catch (JSONException e) { 31 | Log.e("JSONException", "Error: " + e.toString()); 32 | } 33 | } 34 | return controls; 35 | } 36 | 37 | 38 | public Equalizer(Parcel in) 39 | { 40 | controls = new ArrayList<>(); 41 | rawData = in.readString(); 42 | } 43 | 44 | @Override 45 | public int describeContents() 46 | { 47 | return 0; 48 | } 49 | 50 | @Override 51 | public void writeToParcel(Parcel parcel, int i) 52 | { 53 | parcel.writeString(rawData); 54 | } 55 | 56 | public static final Parcelable.Creator CREATOR= new Parcelable.Creator() 57 | { 58 | @Override 59 | public Equalizer createFromParcel(Parcel parcel) 60 | { 61 | return new Equalizer(parcel); 62 | } 63 | 64 | @Override 65 | public Equalizer[] newArray(int i) 66 | { 67 | return new Equalizer[i]; 68 | } 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/mixer/IntegerControl.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control.mixer; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import cz.jiriskorpil.amixerwebui.control.IChannel; 7 | import cz.jiriskorpil.amixerwebui.control.IIntegerControl; 8 | 9 | /** 10 | * Represents integer control (common volume slider). 11 | */ 12 | public class IntegerControl extends MixerControl implements IIntegerControl 13 | { 14 | private IChannel[] channels; 15 | 16 | private int min; 17 | private int max; 18 | private int step; 19 | 20 | /** 21 | * Creates new instance of integer control. 22 | * 23 | * @param jsonObject JSON object representing integer control retrieved from server 24 | * @throws JSONException 25 | */ 26 | public IntegerControl(JSONObject jsonObject) throws JSONException 27 | { 28 | super(jsonObject); 29 | 30 | min = jsonObject.getInt("min"); 31 | max = jsonObject.getInt("max"); 32 | step = jsonObject.getInt("step"); 33 | 34 | channels = new Channel[jsonObject.getJSONArray("channels").length()]; 35 | for (int i = 0; i < channels.length; i++) { 36 | channels[i] = new Channel(this, 37 | jsonObject.getJSONArray("channels").get(i).toString(), 38 | Integer.parseInt(jsonObject.getJSONArray("values").get(i).toString())); 39 | } 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public IChannel[] getChannels() 46 | { 47 | return channels; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | public int getMin() 54 | { 55 | return min; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public int getMax() 62 | { 63 | return max; 64 | } 65 | 66 | /** 67 | * {@inheritDoc} 68 | */ 69 | public int getStep() 70 | { 71 | return step; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 32 | 33 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlParser.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import android.util.Log; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import cz.jiriskorpil.amixerwebui.control.mixer.MixerControl; 12 | 13 | public final class ControlParser 14 | { 15 | private static List controls; 16 | 17 | private ControlParser() {} 18 | 19 | /** 20 | * Parses controls out of JSON array. 21 | * 22 | * @param jsonArray JSON array representing controls retrieved from server 23 | * @return list of control containers 24 | */ 25 | public static List parse(JSONArray jsonArray) 26 | { 27 | Control control; 28 | controls = new ArrayList<>(); 29 | 30 | try { 31 | for (int i = 0; i < jsonArray.length(); i++) 32 | { 33 | control = ControlFactory.createControl(jsonArray.getJSONObject(i)); 34 | if (control instanceof MixerControl) { 35 | getControlContainer(control.getName()).addControl(control); 36 | }/* else { 37 | // not supported (yet?) 38 | }*/ 39 | } 40 | } catch (JSONException e) { 41 | Log.e("JSONException", "Error: " + e.toString()); 42 | } 43 | return controls; 44 | } 45 | 46 | private static ControlContainer getControlContainer(String controlName) 47 | { 48 | int index = findControlContainer(controlName); 49 | 50 | if (index == -1) // if doesn't exists then create new container 51 | { 52 | controls.add(new ControlContainer(controlName)); 53 | index = controls.size() - 1; 54 | } 55 | 56 | return controls.get(index); 57 | } 58 | 59 | private static int findControlContainer(String controlName) 60 | { 61 | for (int i = 0; i < controls.size(); i++) { 62 | if (controls.get(i).getName().equals(ControlContainerType.getContainerName(controlName))) { 63 | return i; 64 | } 65 | } 66 | return -1; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/layout/control_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 21 | 22 | 29 | 30 | 41 | 42 | 53 | 54 | 55 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlContainer.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | /** 4 | * Container for related controls (connects boolean, enumerated and integer control into logical container). 5 | */ 6 | public class ControlContainer 7 | { 8 | private IEnumeratedControl cSource; 9 | private IBooleanControl cSwitch; 10 | private IIntegerControl cVolume; 11 | 12 | private String name; 13 | 14 | /** 15 | * Creates new blank control container. 16 | * 17 | * @param name container or control name (container name is automatically extracted from control name) 18 | */ 19 | public ControlContainer(String name) 20 | { 21 | super(); 22 | this.name = ControlContainerType.getContainerName(name); 23 | } 24 | 25 | /** 26 | * Returns container name. 27 | * 28 | * @return container name 29 | */ 30 | public String getName() 31 | { 32 | return name; 33 | } 34 | 35 | /** 36 | * Adds control into container. Control type is automatically detected based on its name. 37 | * 38 | * @param control control which should be added into container 39 | */ 40 | @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") 41 | public void addControl(Control control) 42 | { 43 | switch (ControlContainerType.fromName(control.getName())) 44 | { 45 | case SOURCE: 46 | cSource = (IEnumeratedControl) control; 47 | break; 48 | case SWITCH: 49 | cSwitch = (IBooleanControl) control; 50 | break; 51 | case VOLUME: 52 | cVolume = (IIntegerControl) control; 53 | break; 54 | } 55 | } 56 | 57 | /** 58 | * Checks whether container contains enumerated control. 59 | * 60 | * @return true if container contains enumerated control 61 | */ 62 | public boolean hasSourceControl() 63 | { 64 | return cSource != null; 65 | } 66 | 67 | /** 68 | * Returns enumerated control. 69 | * 70 | * @return enumerated control 71 | */ 72 | public IEnumeratedControl getSource() 73 | { 74 | return cSource; 75 | } 76 | 77 | /** 78 | * Checks whether container contains boolean control. 79 | * 80 | * @return true if container contains boolean control 81 | */ 82 | public boolean hasSwitchControl() 83 | { 84 | return cSwitch != null; 85 | } 86 | 87 | /** 88 | * Returns boolean control. 89 | * 90 | * @return boolean control 91 | */ 92 | public IBooleanControl getSwitch() 93 | { 94 | return cSwitch; 95 | } 96 | 97 | /** 98 | * Checks whether container contains integer control. 99 | * 100 | * @return true if container contains boolean control 101 | */ 102 | public boolean hasVolumeControl() 103 | { 104 | return cVolume != null; 105 | } 106 | 107 | /** 108 | * Returns integer control. 109 | * 110 | * @return integer control 111 | */ 112 | public IIntegerControl getVolume() 113 | { 114 | return cVolume; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/activity/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.activity; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.ActionBar; 10 | import android.support.v7.app.AppCompatDelegate; 11 | import android.support.v7.widget.Toolbar; 12 | import android.view.MenuInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | 16 | /** 17 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls 18 | * to be used with AppCompat. 19 | */ 20 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity 21 | { 22 | private AppCompatDelegate mDelegate; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) 26 | { 27 | getDelegate().installViewFactory(); 28 | getDelegate().onCreate(savedInstanceState); 29 | super.onCreate(savedInstanceState); 30 | } 31 | 32 | @Override 33 | protected void onPostCreate(Bundle savedInstanceState) 34 | { 35 | super.onPostCreate(savedInstanceState); 36 | getDelegate().onPostCreate(savedInstanceState); 37 | } 38 | 39 | public ActionBar getSupportActionBar() 40 | { 41 | return getDelegate().getSupportActionBar(); 42 | } 43 | 44 | public void setSupportActionBar(@Nullable Toolbar toolbar) 45 | { 46 | getDelegate().setSupportActionBar(toolbar); 47 | } 48 | 49 | @NonNull 50 | @Override 51 | public MenuInflater getMenuInflater() 52 | { 53 | return getDelegate().getMenuInflater(); 54 | } 55 | 56 | @Override 57 | public void setContentView(@LayoutRes int layoutResID) 58 | { 59 | getDelegate().setContentView(layoutResID); 60 | } 61 | 62 | @Override 63 | public void setContentView(View view) 64 | { 65 | getDelegate().setContentView(view); 66 | } 67 | 68 | @Override 69 | public void setContentView(View view, ViewGroup.LayoutParams params) 70 | { 71 | getDelegate().setContentView(view, params); 72 | } 73 | 74 | @Override 75 | public void addContentView(View view, ViewGroup.LayoutParams params) 76 | { 77 | getDelegate().addContentView(view, params); 78 | } 79 | 80 | @Override 81 | protected void onPostResume() 82 | { 83 | super.onPostResume(); 84 | getDelegate().onPostResume(); 85 | } 86 | 87 | @Override 88 | protected void onTitleChanged(CharSequence title, int color) 89 | { 90 | super.onTitleChanged(title, color); 91 | getDelegate().setTitle(title); 92 | } 93 | 94 | @Override 95 | public void onConfigurationChanged(Configuration newConfig) 96 | { 97 | super.onConfigurationChanged(newConfig); 98 | getDelegate().onConfigurationChanged(newConfig); 99 | } 100 | 101 | @Override 102 | protected void onStop() 103 | { 104 | super.onStop(); 105 | getDelegate().onStop(); 106 | } 107 | 108 | @Override 109 | protected void onDestroy() 110 | { 111 | super.onDestroy(); 112 | getDelegate().onDestroy(); 113 | } 114 | 115 | public void invalidateOptionsMenu() 116 | { 117 | getDelegate().invalidateOptionsMenu(); 118 | } 119 | 120 | private AppCompatDelegate getDelegate() 121 | { 122 | if (mDelegate == null) { 123 | mDelegate = AppCompatDelegate.create(this, null); 124 | } 125 | return mDelegate; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/task/AsyncHttpRequestTask.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.task; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.InputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | 10 | /** 11 | * Abstract class for asynchronous HTTP request to the server 12 | */ 13 | abstract public class AsyncHttpRequestTask extends android.os.AsyncTask 14 | { 15 | protected Context context; 16 | protected OnFinishListener mOnFinishListener; 17 | protected String url; 18 | 19 | /** 20 | * Creates new instance of asynchronous task 21 | * 22 | * @param context The context to use. Usually {@link android.app.Activity} object. 23 | * @param url base url directing to server 24 | */ 25 | public AsyncHttpRequestTask(Context context, String url) 26 | { 27 | super(); 28 | this.context = context; 29 | this.url = url; 30 | } 31 | 32 | /** 33 | * Creates new instance of asynchronous task with callback after HTTP response arrives 34 | * 35 | * @param context The context to use. Usually {@link android.app.Activity} object. 36 | * @param url base url directing to server 37 | * @param listener listener which is called after HTTP response arrives 38 | */ 39 | public AsyncHttpRequestTask(Context context, String url, OnFinishListener listener) 40 | { 41 | this(context, url); 42 | this.mOnFinishListener = listener; 43 | } 44 | 45 | /** 46 | * {@inheritDoc} 47 | */ 48 | @Override 49 | abstract protected String doInBackground(String... params); 50 | 51 | /** 52 | * {@inheritDoc} 53 | * 54 | * @param result 55 | */ 56 | @Override 57 | protected void onPostExecute(String result) 58 | { 59 | if (mOnFinishListener != null) { 60 | mOnFinishListener.onFinish(result); 61 | } 62 | } 63 | 64 | /** 65 | * Creates HTTP request to specific URL path with GET method and returns downloaded data. 66 | * 67 | * @param urlString path 68 | * @return downloaded data 69 | */ 70 | protected String downloadFromURL(String urlString) 71 | { 72 | return downloadFromURL(urlString, "GET"); 73 | } 74 | 75 | /** 76 | * Creates HTTP request to specific URL path and returns downloaded data. 77 | * 78 | * @param urlString path 79 | * @param method used method 80 | * @return downloaded data 81 | */ 82 | @SuppressWarnings({"PMD.EmptyCatchBlock", "PMD.CloseResource"}) 83 | protected String downloadFromURL(String urlString, String method) 84 | { 85 | //System.out.println("[" + method + "] Accessing " + this.url + urlString); 86 | 87 | String result = ""; 88 | HttpURLConnection urlConnection = null; 89 | try 90 | { 91 | URL url = new URL(this.url + urlString); 92 | urlConnection = (HttpURLConnection) url.openConnection(); 93 | urlConnection.setRequestMethod(method); 94 | 95 | InputStream in = new BufferedInputStream(urlConnection.getInputStream()); 96 | 97 | int data = in.read(); 98 | while (data != -1) 99 | { 100 | result += (char) data; 101 | data = in.read(); 102 | } 103 | in.close(); 104 | } catch (Exception e) { 105 | //e.printStackTrace(); 106 | } finally { 107 | if (urlConnection != null) { 108 | urlConnection.disconnect(); 109 | } 110 | } 111 | return result; 112 | } 113 | 114 | 115 | public interface OnFinishListener 116 | { 117 | /** 118 | * Called when downloading finished. 119 | * 120 | * @param result downloaded data 121 | */ 122 | void onFinish(String result); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/activity/EqualizerActivity.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.activity; 2 | 3 | import android.content.res.ColorStateList; 4 | import android.content.res.Configuration; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.AppCompatCheckBox; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.view.Menu; 12 | import android.view.MenuItem; 13 | import android.widget.CompoundButton; 14 | 15 | import cz.jiriskorpil.amixerwebui.R; 16 | import cz.jiriskorpil.amixerwebui.control.Equalizer; 17 | import cz.jiriskorpil.amixerwebui.control.EqualizerAdapter; 18 | 19 | /** 20 | * Equalizer activity. 21 | */ 22 | public class EqualizerActivity extends AppCompatActivity { 23 | 24 | private LinearLayoutManager manager; 25 | 26 | private boolean lockedSliders; 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_equalizer); 35 | 36 | if (getSupportActionBar() != null) { 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | } 39 | 40 | Equalizer equalizer = getIntent().getParcelableExtra("equalizer"); 41 | 42 | manager = new LinearLayoutManager(this, getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? LinearLayoutManager.HORIZONTAL : LinearLayoutManager.VERTICAL, false); 43 | 44 | RecyclerView equalizerList = (RecyclerView) findViewById(R.id.equalizer_list); 45 | equalizerList.setLayoutManager(manager); 46 | equalizerList.setAdapter(new EqualizerAdapter(this, equalizer.getControls())); 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | @Override 53 | public void onConfigurationChanged(Configuration newConfig) 54 | { 55 | super.onConfigurationChanged(newConfig); 56 | 57 | manager.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? LinearLayoutManager.HORIZONTAL : LinearLayoutManager.VERTICAL); 58 | } 59 | 60 | /** 61 | * {@inheritDoc} 62 | */ 63 | @Override 64 | public boolean onCreateOptionsMenu(Menu menu) 65 | { 66 | getMenuInflater().inflate(R.menu.menu_equalizer, menu); 67 | 68 | setupCheckbox(menu); 69 | 70 | return true; 71 | } 72 | 73 | /** 74 | * Setups checkbox in menu. 75 | * @param menu menu 76 | */ 77 | private void setupCheckbox(Menu menu) 78 | { 79 | AppCompatCheckBox checkBox = (AppCompatCheckBox) menu.findItem(R.id.equalizer_lock).getActionView(); 80 | ColorStateList colorStateList = new ColorStateList(new int[][]{ 81 | new int[]{-android.R.attr.state_enabled}, // disabled 82 | new int[]{android.R.attr.state_enabled}, // enabled 83 | }, 84 | new int[]{ 85 | Color.WHITE, // disabled 86 | Color.WHITE // enabled 87 | }); 88 | checkBox.setSupportButtonTintList(colorStateList); 89 | checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 90 | @Override 91 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) 92 | { 93 | lockedSliders = b; 94 | } 95 | }); 96 | checkBox.setText(getString(R.string.lock_sliders)); 97 | checkBox.setChecked(true); 98 | checkBox.setPadding(0,0,Math.round(getResources().getDimension(R.dimen.activity_horizontal_margin)),0); 99 | } 100 | 101 | /** 102 | * {@inheritDoc} 103 | */ 104 | @Override 105 | public boolean onOptionsItemSelected(MenuItem item) 106 | { 107 | switch (item.getItemId()) { 108 | // Respond to the action bar's Up/Home button 109 | case android.R.id.home: 110 | finish(); 111 | return true; 112 | 113 | default: 114 | return super.onOptionsItemSelected(item); 115 | } 116 | } 117 | 118 | public boolean isLockedSliders() 119 | { 120 | return lockedSliders; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.activity; 2 | 3 | import android.content.Intent; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.support.design.widget.Snackbar; 7 | import android.support.v4.content.ContextCompat; 8 | import android.support.v4.widget.SwipeRefreshLayout; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.GridLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.Toolbar; 13 | import android.view.Menu; 14 | import android.view.MenuItem; 15 | import android.view.View; 16 | 17 | import cz.jiriskorpil.amixerwebui.R; 18 | 19 | /** 20 | * Main application activity. 21 | */ 22 | public class MainActivity extends AppCompatActivity 23 | { 24 | public static final int EQUALIZER_REQUEST = 1; 25 | 26 | private DataHandler dataHandler; 27 | private String lastUrl; 28 | protected Menu menu; 29 | 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) 32 | { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_main); 35 | 36 | setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); 37 | 38 | dataHandler = new DataHandler(this, 39 | (RecyclerView) findViewById(R.id.controls_list), 40 | (SwipeRefreshLayout) findViewById(R.id.swipe_container)) 41 | .setOnFailListener(new DataHandler.OnFailListener() 42 | { 43 | @Override 44 | public void onFail() 45 | { 46 | Snackbar 47 | .make(findViewById(android.R.id.content), 48 | getResources().getString(R.string.msg_connection_error), 49 | Snackbar.LENGTH_LONG) 50 | .setAction(getResources().getString(R.string.retry_btn_title), new View.OnClickListener() 51 | { 52 | @Override 53 | public void onClick(View v) 54 | { 55 | dataHandler.download(); 56 | } 57 | }).setActionTextColor(ContextCompat.getColor(getBaseContext(), R.color.colorPrimaryLight)) 58 | .show(); 59 | } 60 | }); 61 | lastUrl = DataHandler.getBaseUrl(this); 62 | } 63 | 64 | /** 65 | * {@inheritDoc} 66 | */ 67 | @Override 68 | public void onConfigurationChanged(Configuration newConfig) 69 | { 70 | super.onConfigurationChanged(newConfig); 71 | 72 | GridLayoutManager manager = (GridLayoutManager) ((RecyclerView) findViewById(R.id.controls_list)).getLayoutManager(); 73 | manager.setSpanCount(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? 2 : 1); 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | @Override 80 | public void onResume() 81 | { 82 | super.onResume(); 83 | 84 | if (!lastUrl.equals(DataHandler.getBaseUrl(this))) 85 | { 86 | dataHandler.download(); 87 | lastUrl = DataHandler.getBaseUrl(this); 88 | } 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public boolean onCreateOptionsMenu(Menu menu) 96 | { 97 | this.menu = menu; 98 | getMenuInflater().inflate(R.menu.menu_main, menu); 99 | 100 | dataHandler.download(); 101 | 102 | return true; 103 | } 104 | 105 | /** 106 | * {@inheritDoc} 107 | */ 108 | @Override 109 | public boolean onOptionsItemSelected(MenuItem item) 110 | { 111 | int id = item.getItemId(); 112 | 113 | switch (id) 114 | { 115 | case R.id.action_settings: 116 | startActivity(new Intent(this, SettingsActivity.class)); 117 | return true; 118 | 119 | case R.id.action_equalizer: 120 | Intent intent = new Intent(this, EqualizerActivity.class); 121 | intent.putExtra("equalizer", dataHandler.getEqualizer()); 122 | startActivityForResult(intent, EQUALIZER_REQUEST); 123 | return true; 124 | 125 | default: 126 | return super.onOptionsItemSelected(item); 127 | } 128 | } 129 | 130 | @SuppressWarnings("PMD.MissingBreakInSwitch") 131 | @Override 132 | protected void onActivityResult(int requestCode, int resultCode, Intent data) 133 | { 134 | switch (requestCode) 135 | { 136 | case EQUALIZER_REQUEST: 137 | dataHandler.setupEqualizer(); 138 | break; 139 | 140 | default: 141 | super.onActivityResult(requestCode, resultCode, data); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/EqualizerAdapter.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | import android.widget.SeekBar; 11 | import android.widget.TextView; 12 | 13 | import java.util.List; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | 17 | import cz.jiriskorpil.amixerwebui.R; 18 | import cz.jiriskorpil.amixerwebui.activity.DataHandler; 19 | import cz.jiriskorpil.amixerwebui.activity.EqualizerActivity; 20 | import cz.jiriskorpil.amixerwebui.task.ChangeEqualizerHttpRequestTask; 21 | 22 | /** 23 | * List adapter for equalizer control container. 24 | */ 25 | public class EqualizerAdapter extends RecyclerView.Adapter 26 | { 27 | private Context context; 28 | private List data = null; 29 | 30 | /** 31 | * @param context The context to use. Usually {@link android.app.Activity} object. 32 | * @param data list of controls 33 | */ 34 | public EqualizerAdapter(Context context, List data) 35 | { 36 | this.context = context; 37 | this.data = data; 38 | } 39 | 40 | /** 41 | * {@inheritDoc} 42 | */ 43 | @Override 44 | public int getItemCount() 45 | { 46 | return data.size(); 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | @Override 53 | public void onBindViewHolder(EqualizerAdapter.ViewHolder holder, int position) 54 | { 55 | setupEqualizerRow(data.get(position), holder); 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | @Override 62 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 63 | { 64 | View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.equalizer_row, parent, false); 65 | 66 | return new ViewHolder(itemView); 67 | } 68 | 69 | /** 70 | * Setups equalizer row. 71 | * 72 | * @param controlContainer control container 73 | * @param holder control container holder 74 | */ 75 | private void setupEqualizerRow(ControlContainer controlContainer, final ViewHolder holder) 76 | { 77 | if (!controlContainer.hasVolumeControl()) { 78 | return; 79 | } 80 | 81 | holder.channelsList.removeAllViews(); 82 | 83 | final EqualizerActivity equalizerActivity = (EqualizerActivity) context; 84 | 85 | final IIntegerControl volume = controlContainer.getVolume(); 86 | 87 | String name = controlContainer.getName(); 88 | Pattern p = Pattern.compile("[0-9]+\\. ([0-9]+ k?Hz) .*"); 89 | Matcher m = p.matcher(name); 90 | holder.channelName.setText(m.find() ? m.group(1) : name); 91 | 92 | for (final IChannel channel: volume.getChannels()) 93 | { 94 | View channelView = ((Activity) context).getLayoutInflater().inflate(R.layout.equalizer_channel, holder.channelsList, false); 95 | 96 | SeekBar channelVolumeSeekBar = (SeekBar) channelView.findViewById(R.id.channel_volume_seek_bar); 97 | channelVolumeSeekBar.setMax(volume.getMax()); 98 | channelVolumeSeekBar.setProgress(channel.getValue()); 99 | channelVolumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 100 | @Override 101 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) 102 | { 103 | if (fromUser && equalizerActivity.isLockedSliders()) 104 | { 105 | for (int i = 0; i < volume.getChannels().length; i++) 106 | { 107 | if (!holder.channelsList.getChildAt(i).equals(seekBar)) 108 | { 109 | volume.getChannels()[i].setValue(progress); 110 | View channelView = holder.channelsList.getChildAt(i); 111 | ((SeekBar) channelView.findViewById(R.id.channel_volume_seek_bar)) 112 | .setProgress(progress); 113 | } 114 | } 115 | } 116 | if (fromUser) 117 | { 118 | channel.setValue(progress); 119 | } 120 | } 121 | 122 | @SuppressWarnings("PMD.UncommentedEmptyMethodBody") 123 | @Override 124 | public void onStartTrackingTouch(SeekBar seekBar) 125 | { 126 | } 127 | 128 | @Override 129 | public void onStopTrackingTouch(SeekBar seekBar) 130 | { 131 | String volumes = ""; 132 | for (int i = 0; i < volume.getChannels().length; i++) 133 | { 134 | if (!"".equals(volumes)) { 135 | volumes += "/"; 136 | } 137 | volumes += String.valueOf(volume.getChannels()[i].getValue()); 138 | } 139 | 140 | new ChangeEqualizerHttpRequestTask(holder.v.getContext(), DataHandler.getBaseUrl(holder.v.getContext())) 141 | .execute(String.valueOf(volume.getId()), volumes); 142 | } 143 | }); 144 | 145 | holder.channelsList.addView(channelView); 146 | } 147 | } 148 | 149 | 150 | /** 151 | * Holds GUI controls. 152 | */ 153 | static class ViewHolder extends RecyclerView.ViewHolder 154 | { 155 | View v; 156 | 157 | LinearLayout channelsList; 158 | TextView channelName; 159 | 160 | ViewHolder(View v) 161 | { 162 | super(v); 163 | this.v = v; 164 | channelsList = (LinearLayout) v.findViewById(R.id.channels_list); 165 | channelName = (TextView) v.findViewById(R.id.channel_name); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/activity/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.activity; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.content.res.Configuration; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.preference.ListPreference; 11 | import android.preference.Preference; 12 | import android.preference.PreferenceActivity; 13 | import android.support.v7.app.ActionBar; 14 | import android.preference.PreferenceFragment; 15 | import android.preference.PreferenceManager; 16 | import android.view.MenuItem; 17 | 18 | import java.util.List; 19 | 20 | import cz.jiriskorpil.amixerwebui.R; 21 | 22 | /** 23 | * A {@link PreferenceActivity} that presents a set of application settings. On 24 | * handset devices, settings are presented as a single list. On tablets, 25 | * settings are split by category, with category headers shown to the left of 26 | * the list of settings. 27 | *

28 | * See 29 | * Android Design: Settings for design guidelines and the Settings 31 | * API Guide for more information on developing a Settings UI. 32 | */ 33 | public class SettingsActivity extends AppCompatPreferenceActivity 34 | { 35 | /** 36 | * A preference value change listener that updates the preference's summary 37 | * to reflect its new value. 38 | */ 39 | private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() 40 | { 41 | @Override 42 | public boolean onPreferenceChange(Preference preference, Object value) 43 | { 44 | String stringValue = value.toString(); 45 | 46 | if (preference instanceof ListPreference) 47 | { 48 | // For list preferences, look up the correct display value in 49 | // the preference's 'entries' list. 50 | ListPreference listPreference = (ListPreference) preference; 51 | int index = listPreference.findIndexOfValue(stringValue); 52 | 53 | // Set the summary to reflect the new value. 54 | preference.setSummary( 55 | index >= 0 56 | ? listPreference.getEntries()[index] 57 | : null); 58 | 59 | } else { 60 | // For all other preferences, set the summary to the value's 61 | // simple string representation. 62 | preference.setSummary(stringValue); 63 | } 64 | return true; 65 | } 66 | }; 67 | 68 | /** 69 | * Helper method to determine if the device has an extra-large screen. For 70 | * example, 10" tablets are extra-large. 71 | */ 72 | private static boolean isXLargeTablet(Context context) 73 | { 74 | return (context.getResources().getConfiguration().screenLayout 75 | & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE; 76 | } 77 | 78 | /** 79 | * Binds a preference's summary to its value. More specifically, when the 80 | * preference's value is changed, its summary (line of text below the 81 | * preference title) is updated to reflect the value. The summary is also 82 | * immediately updated upon calling this method. The exact display format is 83 | * dependent on the type of preference. 84 | * 85 | * @see #sBindPreferenceSummaryToValueListener 86 | */ 87 | private static void bindPreferenceSummaryToValue(Preference preference) 88 | { 89 | // Set the listener to watch for value changes. 90 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); 91 | 92 | // Trigger the listener immediately with the preference's 93 | // current value. 94 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, 95 | PreferenceManager 96 | .getDefaultSharedPreferences(preference.getContext()) 97 | .getString(preference.getKey(), "")); 98 | } 99 | 100 | @Override 101 | protected void onCreate(Bundle savedInstanceState) 102 | { 103 | super.onCreate(savedInstanceState); 104 | setupActionBar(); 105 | } 106 | 107 | /** 108 | * Set up the {@link android.app.ActionBar}, if the API is available. 109 | */ 110 | private void setupActionBar() 111 | { 112 | ActionBar actionBar = getSupportActionBar(); 113 | if (actionBar != null) { 114 | // Show the Up button in the action bar. 115 | actionBar.setDisplayHomeAsUpEnabled(true); 116 | } 117 | } 118 | 119 | /** 120 | * {@inheritDoc} 121 | */ 122 | @Override 123 | public boolean onIsMultiPane() 124 | { 125 | return isXLargeTablet(this); 126 | } 127 | 128 | /** 129 | * {@inheritDoc} 130 | */ 131 | @Override 132 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 133 | public void onBuildHeaders(List

target) 134 | { 135 | loadHeadersFromResource(R.xml.pref_headers, target); 136 | } 137 | 138 | /** 139 | * This method stops fragment injection in malicious applications. 140 | * Make sure to deny any unknown fragments here. 141 | */ 142 | protected boolean isValidFragment(String fragmentName) 143 | { 144 | return PreferenceFragment.class.getName().equals(fragmentName) 145 | || ConnectionPreferenceFragment.class.getName().equals(fragmentName) 146 | || AboutFragment.class.getName().equals(fragmentName); 147 | } 148 | 149 | /** 150 | * This fragment shows connection preferences only. It is used when the 151 | * activity is showing a two-pane settings UI. 152 | */ 153 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 154 | public static class ConnectionPreferenceFragment extends PreferenceFragment 155 | { 156 | @Override 157 | public void onCreate(Bundle savedInstanceState) 158 | { 159 | super.onCreate(savedInstanceState); 160 | addPreferencesFromResource(R.xml.pref_connection); 161 | setHasOptionsMenu(true); 162 | 163 | bindPreferenceSummaryToValue(findPreference("ip_address")); 164 | bindPreferenceSummaryToValue(findPreference("port")); 165 | } 166 | } 167 | 168 | /** 169 | * This fragment shows data and sync preferences only. It is used when the 170 | * activity is showing a two-pane settings UI. 171 | */ 172 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 173 | public static class AboutFragment extends PreferenceFragment 174 | { 175 | @SuppressWarnings("PMD.EmptyCatchBlock") 176 | @Override 177 | public void onCreate(Bundle savedInstanceState) 178 | { 179 | super.onCreate(savedInstanceState); 180 | addPreferencesFromResource(R.xml.pref_about); 181 | setHasOptionsMenu(true); 182 | 183 | findPreference("author").setSummary(getResources().getString(R.string.author)); 184 | 185 | PackageManager pManager = getActivity().getPackageManager(); 186 | 187 | try 188 | { 189 | PackageInfo pInfo = pManager.getPackageInfo(getActivity().getPackageName(), 0); 190 | findPreference("version").setSummary(pInfo.versionName); 191 | } catch (PackageManager.NameNotFoundException e) { } 192 | } 193 | } 194 | 195 | @Override 196 | public boolean onOptionsItemSelected(MenuItem item) 197 | { 198 | int id = item.getItemId(); 199 | if (id == android.R.id.home) 200 | { 201 | onBackPressed(); 202 | return true; 203 | } 204 | return super.onOptionsItemSelected(item); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/activity/DataHandler.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.activity; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.Configuration; 6 | import android.preference.PreferenceManager; 7 | import android.support.v4.widget.SwipeRefreshLayout; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.support.v7.widget.RecyclerView; 10 | import android.util.Log; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.widget.AdapterView; 14 | import android.widget.ArrayAdapter; 15 | import android.widget.Spinner; 16 | 17 | import org.json.JSONArray; 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Iterator; 23 | import java.util.List; 24 | 25 | import cz.jiriskorpil.amixerwebui.R; 26 | import cz.jiriskorpil.amixerwebui.control.ControlContainer; 27 | import cz.jiriskorpil.amixerwebui.control.ControlContainerAdapter; 28 | import cz.jiriskorpil.amixerwebui.control.ControlParser; 29 | import cz.jiriskorpil.amixerwebui.control.Equalizer; 30 | import cz.jiriskorpil.amixerwebui.task.AsyncHttpRequestTask; 31 | import cz.jiriskorpil.amixerwebui.task.ChangeCardHttpRequestTask; 32 | import cz.jiriskorpil.amixerwebui.task.RetrieveCardHttpRequestTask; 33 | import cz.jiriskorpil.amixerwebui.task.RetrieveCardsHttpRequestTask; 34 | import cz.jiriskorpil.amixerwebui.task.RetrieveControlsHttpRequestTask; 35 | import cz.jiriskorpil.amixerwebui.task.RetrieveEqualizerHttpRequestTask; 36 | import cz.jiriskorpil.amixerwebui.task.RetrieveHostnameHttpRequestTask; 37 | 38 | /** 39 | * Class which supplies (main) activity with data. 40 | */ 41 | public class DataHandler 42 | { 43 | private Context context; 44 | private OnFailListener mOnFailListener; 45 | 46 | /* GUI components */ 47 | private RecyclerView listView; 48 | private SwipeRefreshLayout swipeRefreshLayout; 49 | 50 | private boolean downloadEnabled = true; 51 | private String lastUrl = ""; 52 | private String cardId = ""; 53 | 54 | private Equalizer equalizer; 55 | 56 | /** 57 | * @param context The context to use. Usually {@link android.app.Activity} object. 58 | * @param resultListView ListView for results 59 | * @param swipeRefreshLayout layout for results 60 | */ 61 | DataHandler(Context context, RecyclerView resultListView, SwipeRefreshLayout swipeRefreshLayout) 62 | { 63 | this.context = context; 64 | 65 | this.listView = resultListView; 66 | this.listView.setHasFixedSize(true); 67 | this.listView.setLayoutManager(new GridLayoutManager(context, context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE ? 2 : 1, GridLayoutManager.VERTICAL, false)); 68 | 69 | this.swipeRefreshLayout = swipeRefreshLayout; 70 | this.swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() 71 | { 72 | @Override 73 | public void onRefresh() 74 | { 75 | download(); 76 | } 77 | }); 78 | } 79 | 80 | /** 81 | * Creates request to download data 82 | * @return self 83 | */ 84 | DataHandler download() 85 | { 86 | if (!downloadEnabled) { 87 | return this; 88 | } 89 | downloadEnabled = false; 90 | 91 | swipeRefreshLayout.post(new Runnable() 92 | { 93 | @Override 94 | public void run() 95 | { 96 | swipeRefreshLayout.setRefreshing(true); 97 | new RetrieveControlsHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() 98 | { 99 | @Override 100 | public void onFinish(String result) 101 | { 102 | try 103 | { 104 | downloadEnabled = true; 105 | lastUrl = getBaseUrl(context); 106 | displayData("".equals(result) ? null : new JSONArray(result)); 107 | } catch (JSONException e) { 108 | Log.e("JSONException", "Error: " + e.toString()); 109 | } 110 | } 111 | }).execute(); 112 | 113 | if (!lastUrl.equals(getBaseUrl(context))) 114 | { 115 | cardId = ""; 116 | setupHostname(); 117 | setupSoundCards(); 118 | } 119 | setupEqualizer(); 120 | } 121 | }); 122 | return this; 123 | } 124 | 125 | private void setupHostname() 126 | { 127 | final MainActivity activity = (MainActivity) context; 128 | if (activity.getSupportActionBar() != null) { 129 | activity.getSupportActionBar().setSubtitle(""); 130 | } 131 | 132 | new RetrieveHostnameHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() { 133 | @Override 134 | public void onFinish(String result) { 135 | activity.getSupportActionBar().setSubtitle(result); 136 | } 137 | }).execute(); 138 | } 139 | 140 | private void setupSoundCards() 141 | { 142 | final MenuItem soundCard = ((MainActivity) context).menu.findItem(R.id.action_card); 143 | soundCard.setVisible(false); 144 | new RetrieveCardsHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() { 145 | @Override 146 | public void onFinish(String result) { 147 | try 148 | { 149 | JSONObject cards = "[]".equals(result) ? new JSONObject() : (JSONObject) (new JSONArray("[" + result + "]")).get(0); 150 | 151 | if (cards.length() > 1) { 152 | soundCard.setVisible(true); 153 | 154 | final List cardIds = new ArrayList<>(); 155 | List cardNames = new ArrayList<>(); 156 | 157 | Iterator iter = cards.keys(); 158 | while (iter.hasNext()) { 159 | String id = iter.next(); 160 | cardNames.add((String) cards.get(id)); 161 | cardIds.add(id); 162 | } 163 | 164 | final Spinner spinner = (Spinner) soundCard.getActionView(); 165 | ArrayAdapter adapter = new ArrayAdapter<>(context, R.layout.spinner_item, cardNames); 166 | adapter.setDropDownViewResource(R.layout.spinner_dropdown_item); 167 | spinner.setAdapter(adapter); 168 | 169 | spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 170 | @Override 171 | public void onItemSelected(AdapterView adapterView, View view, final int i, long l) { 172 | if (cardId.equals(cardIds.get(i)) || "".equals(cardId)) { 173 | return; 174 | } 175 | 176 | (new ChangeCardHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() { 177 | @Override 178 | public void onFinish(String result) { 179 | download(); 180 | } 181 | })).execute(cardIds.get(i)); 182 | cardId = cardIds.get(i); 183 | } 184 | 185 | @SuppressWarnings("PMD.UncommentedEmptyMethodBody") 186 | @Override 187 | public void onNothingSelected(AdapterView adapterView) { 188 | } 189 | }); 190 | 191 | new RetrieveCardHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() { 192 | @Override 193 | public void onFinish(String result) { 194 | cardId = result; 195 | spinner.setSelection(cardIds.indexOf(result), false); 196 | } 197 | }).execute(); 198 | } 199 | } catch (JSONException e) { 200 | Log.e("JSONException", "Error: " + e.toString()); 201 | } 202 | } 203 | }).execute(); 204 | } 205 | 206 | void setupEqualizer() 207 | { 208 | final MainActivity activity = (MainActivity) context; 209 | 210 | new RetrieveEqualizerHttpRequestTask(context, getBaseUrl(context), new AsyncHttpRequestTask.OnFinishListener() { 211 | @Override 212 | public void onFinish(String result) { 213 | try 214 | { 215 | JSONArray data = new JSONArray(result); 216 | if (data.length() > 0) { 217 | activity.menu.findItem(R.id.action_equalizer).setVisible(true); 218 | equalizer = new Equalizer(result); 219 | } 220 | } catch (JSONException e) { 221 | Log.e("JSONException", "Error: " + e.toString()); 222 | } 223 | } 224 | }).execute(); 225 | } 226 | 227 | DataHandler setOnFailListener(OnFailListener listener) 228 | { 229 | mOnFailListener = listener; 230 | return this; 231 | } 232 | 233 | /** 234 | * Displays downloaded data. 235 | * 236 | * @param jsonArray JSON array representing controls retrieved from server 237 | */ 238 | private void displayData(JSONArray jsonArray) 239 | { 240 | List controls = new ArrayList<>(); 241 | 242 | if (jsonArray == null) { 243 | if (mOnFailListener != null) { 244 | mOnFailListener.onFail(); 245 | lastUrl = ""; 246 | cardId = ""; 247 | } 248 | } else { 249 | controls = ControlParser.parse(jsonArray); 250 | } 251 | 252 | listView.setAdapter(new ControlContainerAdapter(context, controls)); 253 | swipeRefreshLayout.setRefreshing(false); 254 | } 255 | 256 | /** 257 | * Returns base url to server (http://:) 258 | * 259 | * @return server url 260 | */ 261 | public static String getBaseUrl(Context context) 262 | { 263 | SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); 264 | String ipAddress = preferences.getString("ip_address", context.getResources().getString(R.string.pref_default_ip_address)); 265 | String port = preferences.getString("port", context.getResources().getString(R.string.pref_default_port)); 266 | return "http://" + ipAddress + ":" + port; 267 | } 268 | 269 | public Equalizer getEqualizer() { 270 | return equalizer; 271 | } 272 | 273 | interface OnFailListener 274 | { 275 | /** 276 | * Called when downloading failed. 277 | */ 278 | void onFail(); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /app/src/main/java/cz/jiriskorpil/amixerwebui/control/ControlContainerAdapter.java: -------------------------------------------------------------------------------- 1 | package cz.jiriskorpil.amixerwebui.control; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.SwitchCompat; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.CheckBox; 11 | import android.widget.LinearLayout; 12 | import android.widget.RadioButton; 13 | import android.widget.RadioGroup; 14 | import android.widget.SeekBar; 15 | import android.widget.TextView; 16 | import android.widget.Toast; 17 | 18 | import java.util.List; 19 | 20 | import cz.jiriskorpil.amixerwebui.R; 21 | import cz.jiriskorpil.amixerwebui.activity.DataHandler; 22 | import cz.jiriskorpil.amixerwebui.task.ChangeSourceHttpRequestTask; 23 | import cz.jiriskorpil.amixerwebui.task.ChangeVolumeHttpRequestTask; 24 | import cz.jiriskorpil.amixerwebui.task.ToggleControlHttpRequestTask; 25 | 26 | /** 27 | * List adapter for control container. 28 | */ 29 | public class ControlContainerAdapter extends RecyclerView.Adapter 30 | { 31 | private Context context; 32 | private List data = null; 33 | 34 | private static final float FULL_VISIBILITY = 1; 35 | private static final float LOW_VISIBILITY = (float) 0.4; 36 | 37 | /** 38 | * @param context The context to use. Usually {@link android.app.Activity} object. 39 | * @param data list of controls 40 | */ 41 | public ControlContainerAdapter(Context context, List data) 42 | { 43 | this.context = context; 44 | this.data = data; 45 | } 46 | 47 | @Override 48 | public int getItemCount() 49 | { 50 | return data.size(); 51 | } 52 | 53 | @Override 54 | public void onBindViewHolder(ControlContainerHolder holder, int i) 55 | { 56 | ControlContainer container = data.get(i); 57 | 58 | setupCard(container, holder); 59 | } 60 | 61 | @Override 62 | public ControlContainerHolder onCreateViewHolder(ViewGroup viewGroup, int i) 63 | { 64 | View itemView = LayoutInflater 65 | .from(viewGroup.getContext()) 66 | .inflate(R.layout.control_row, viewGroup, false); 67 | 68 | return new ControlContainerHolder(itemView); 69 | } 70 | 71 | /** 72 | * Setups control card. 73 | * 74 | * @param container control container 75 | * @param holder control container holder 76 | */ 77 | private void setupCard(ControlContainer container, ControlContainerHolder holder) 78 | { 79 | setupCardHeader(container, holder); 80 | 81 | setupCardBodySource(container, holder); 82 | setupCardBodyChannels(container, holder); 83 | 84 | setCardBodyAlpha(container, holder); 85 | } 86 | 87 | /** 88 | * Sets card body alpha color part based on boolean control value. 89 | * 90 | * @param container control container 91 | * @param holder control container holder 92 | */ 93 | private void setCardBodyAlpha(ControlContainer container, ControlContainerHolder holder) 94 | { 95 | if (container.hasSwitchControl()) { 96 | if (container.getSwitch().isChecked()) 97 | { 98 | holder.source_list.setAlpha(FULL_VISIBILITY); 99 | holder.channels_list.setAlpha(FULL_VISIBILITY); 100 | } else 101 | { 102 | holder.source_list.setAlpha(LOW_VISIBILITY); 103 | holder.channels_list.setAlpha(LOW_VISIBILITY); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Changes boolean control value including initialize of request to server. 110 | * 111 | * @param container control container 112 | * @param holder control container holder 113 | * @param isChecked true if boolean control is checked else false 114 | */ 115 | private void toggleControl(final ControlContainer container, final ControlContainerHolder holder, boolean isChecked) 116 | { 117 | container.getSwitch().setChecked(isChecked); 118 | setCardBodyAlpha(container, holder); 119 | new ToggleControlHttpRequestTask(holder.getView().getContext(), DataHandler.getBaseUrl(holder.getView().getContext())) 120 | .execute(String.valueOf(container.getSwitch().getId()), container.getSwitch().isChecked() ? "1" : "0"); 121 | } 122 | 123 | /** 124 | * Setups card header (Switch, TextView and CheckBox values, settings and listeners). 125 | * 126 | * @param container control container 127 | * @param holder control container holder 128 | */ 129 | private void setupCardHeader(final ControlContainer container, final ControlContainerHolder holder) 130 | { 131 | if (container.hasSwitchControl()) // set check value and add onClick listener 132 | { 133 | holder.enabled.setVisibility(View.VISIBLE); 134 | holder.enabled.setChecked(container.getSwitch().isChecked()); 135 | holder.enabled.setOnClickListener(new View.OnClickListener() 136 | { 137 | @Override 138 | public void onClick(View v) 139 | { 140 | toggleControl(container, holder, holder.enabled.isChecked()); 141 | } 142 | }); 143 | holder.name.setOnClickListener(new View.OnClickListener() 144 | { 145 | @Override 146 | public void onClick(View v) 147 | { 148 | toggleControl(container, holder, !holder.enabled.isChecked()); 149 | holder.enabled.setChecked(!holder.enabled.isChecked()); 150 | } 151 | }); 152 | } else { // or hide the switch 153 | holder.enabled.setVisibility(View.INVISIBLE); 154 | } 155 | 156 | holder.name.setText(container.getName()); 157 | 158 | if (container.hasVolumeControl()) // if there is volume control then display bind checkbox and add listener 159 | { 160 | holder.bind_sliders.setVisibility(View.VISIBLE); 161 | holder.bind_sliders.setOnLongClickListener(new View.OnLongClickListener() 162 | { 163 | @Override 164 | public boolean onLongClick(View v) 165 | { 166 | Toast.makeText(holder.getView().getContext(), 167 | holder.getView().getContext().getResources().getString(holder.bind_sliders.isChecked() ? R.string.sliders_locked : R.string.sliders_unlocked), 168 | Toast.LENGTH_LONG).show(); 169 | return true; 170 | } 171 | }); 172 | } else { 173 | holder.bind_sliders.setVisibility(View.INVISIBLE); 174 | } 175 | } 176 | 177 | /** 178 | * Setups part of cards body - source radio buttons (values, settings and listeners). 179 | * 180 | * @param container control container 181 | * @param holder control container holder 182 | */ 183 | private void setupCardBodySource(final ControlContainer container, final ControlContainerHolder holder) 184 | { 185 | holder.source_list.removeAllViews(); 186 | if (container.hasSourceControl()) 187 | { 188 | holder.source_list.setVisibility(View.VISIBLE); 189 | holder.source_list.check(0); 190 | for (int i = 0; i < container.getSource().getItems().length; i++) 191 | { 192 | final RadioButton button = new RadioButton(holder.getView().getContext()); 193 | button.setId(i + 1); 194 | button.setText(container.getSource().getItems()[i]); 195 | button.setOnClickListener(new View.OnClickListener() 196 | { 197 | @Override 198 | public void onClick(View v) 199 | { 200 | container.getSource().setValue(holder.source_list.getCheckedRadioButtonId()); 201 | new ChangeSourceHttpRequestTask(holder.getView().getContext(), DataHandler.getBaseUrl(holder.getView().getContext())) 202 | .execute(String.valueOf(container.getSource().getId()), String.valueOf(button.getId())); 203 | } 204 | }); 205 | holder.source_list.addView(button); 206 | } 207 | holder.source_list.check(container.getSource().getValue() + 1); 208 | } else { 209 | holder.source_list.setVisibility(View.INVISIBLE); 210 | } 211 | } 212 | 213 | /** 214 | * Setups part of cards body - channels (values, settings nad listeners). 215 | * 216 | * @param container control container 217 | * @param holder control container holder 218 | */ 219 | private void setupCardBodyChannels(final ControlContainer container, final ControlContainerHolder holder) 220 | { 221 | holder.channels_list.removeAllViews(); 222 | if (container.hasVolumeControl()) 223 | { 224 | for (int i = 0; i < container.getVolume().getChannels().length; i++) 225 | { 226 | LinearLayout channelLayout = (LinearLayout) holder.getView().findViewById(R.id.control_channel); 227 | View channelView = ((Activity) context).getLayoutInflater().inflate(R.layout.control_channel, channelLayout, false); 228 | holder.channels_list.addView(channelView, i); 229 | 230 | TextView channelName = (TextView) channelView.findViewById(R.id.channel_name); 231 | SeekBar channelVolumeSeekBar = (SeekBar) channelView.findViewById(R.id.channel_volume_seek_bar); 232 | final TextView channelVolume = (TextView) channelView.findViewById(R.id.channel_volume); 233 | 234 | final IChannel channel = container.getVolume().getChannels()[i]; 235 | 236 | channelName.setText(channel.getName()); 237 | channelVolumeSeekBar.setMax(channel.getControl().getMax()); 238 | channelVolumeSeekBar.setProgress(channel.getValue()); 239 | channelVolume.setText(holder.getView().getContext().getString(R.string.channel_value, Math.round(100 * (double) channel.getValue() / channel.getControl().getMax()))); 240 | 241 | channelVolumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() 242 | { 243 | @Override 244 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) 245 | { 246 | String progressMsg = holder.getView().getContext().getString(R.string.channel_value, Math.round(100 * progress / seekBar.getMax())); 247 | if (fromUser && holder.bind_sliders.isChecked()) { 248 | for (int j = 0; j < container.getVolume().getChannels().length; j++) { 249 | if (!holder.channels_list.getChildAt(j).equals(seekBar)) 250 | { 251 | container.getVolume().getChannels()[j].setValue(progress); 252 | View channelView = holder.channels_list.getChildAt(j); 253 | ((SeekBar) channelView.findViewById(R.id.channel_volume_seek_bar)) 254 | .setProgress(progress); 255 | ((TextView) channelView.findViewById(R.id.channel_volume)) 256 | .setText(progressMsg); 257 | } 258 | } 259 | } 260 | if (fromUser) 261 | { 262 | channel.setValue(progress); 263 | channelVolume.setText(progressMsg); 264 | } 265 | } 266 | 267 | @SuppressWarnings("PMD.UncommentedEmptyMethodBody") 268 | @Override 269 | public void onStartTrackingTouch(SeekBar seekBar) 270 | { 271 | } 272 | 273 | @Override 274 | public void onStopTrackingTouch(SeekBar seekBar) 275 | { 276 | String volumes = ""; 277 | for (int j = 0; j < container.getVolume().getChannels().length; j++) 278 | { 279 | if (!"".equals(volumes)) { 280 | volumes += "/"; 281 | } 282 | volumes += String.valueOf(container.getVolume().getChannels()[j].getValue()); 283 | } 284 | new ChangeVolumeHttpRequestTask(holder.getView().getContext(), DataHandler.getBaseUrl(holder.getView().getContext())) 285 | .execute(String.valueOf(container.getVolume().getId()), volumes); 286 | } 287 | }); 288 | } 289 | } 290 | } 291 | 292 | /** 293 | * Holds ControlContainer GUI controls. 294 | */ 295 | static class ControlContainerHolder extends RecyclerView.ViewHolder 296 | { 297 | View v; 298 | 299 | SwitchCompat enabled; 300 | TextView name; 301 | CheckBox bind_sliders; 302 | RadioGroup source_list; 303 | LinearLayout channels_list; 304 | 305 | ControlContainerHolder(View v) 306 | { 307 | super(v); 308 | this.v = v; 309 | enabled = (SwitchCompat) v.findViewById(R.id.enabled); 310 | name = (TextView) v.findViewById(R.id.name); 311 | bind_sliders = (CheckBox) v.findViewById(R.id.bind_sliders); 312 | source_list = (RadioGroup) v.findViewById(R.id.source_list); 313 | channels_list = (LinearLayout) v.findViewById(R.id.channels_list); 314 | } 315 | 316 | 317 | public View getView() 318 | { 319 | return v; 320 | } 321 | } 322 | } 323 | --------------------------------------------------------------------------------