Initial commit

This commit is contained in:
kaytat 2015-10-16 22:02:20 -07:00
parent fb83ca531c
commit 38c830135f
58 changed files with 3050 additions and 0 deletions

36
.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# Built application files
*.apk
*.ap_
# Files for the Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
# Gradle files
.gradle/
build/
*/build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
.idea

92
SimpleProtocolPlayer.iml Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id="SimpleProtocolPlayer" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<afterSyncTasks>
<task>generateDebugAndroidTestSources</task>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 17 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

35
build.gradle Normal file
View File

@ -0,0 +1,35 @@
apply plugin: 'com.android.application'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
}
}
allprojects {
repositories {
jcenter()
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 17
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "com.kaytat.simpleprotocolplayer"
minSdkVersion 14
targetSdkVersion 17
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
}
}
productFlavors {
}
}
dependencies {
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Sun May 10 15:47:19 PDT 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-bin.zip

164
gradlew vendored Executable file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

0
proguard-project.txt Normal file
View File

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Copyright (C) 2014 kaytat
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kaytat.simpleprotocolplayer"
android:versionCode="5"
android:versionName="0.4.0.0">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_title" >
<activity
android:name="com.kaytat.simpleprotocolplayer.MainActivity"
android:label="@string/app_title"
android:theme="@android:style/Theme.Holo.Light" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.kaytat.simpleprotocolplayer.NoticeActivity"
android:label="@string/notice_title"
android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
</activity>
<service
android:name="com.kaytat.simpleprotocolplayer.MusicService"
android:exported="false" >
<intent-filter>
<action android:name="com.kaytat.simpleprotocolplayer.action.PLAY" />
<action android:name="com.kaytat.simpleprotocolplayer.action.STOP" />
</intent-filter>
</service>
<receiver android:name="com.kaytat.simpleprotocolplayer.MusicIntentReceiver" >
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Copyright (C) 2014 kaytat
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kaytat.simpleprotocolplayer"
android:versionCode="4"
android:versionName="0.3.0.0">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_title" >
<activity
android:name="com.kaytat.simpleprotocolplayer.MainActivity"
android:label="@string/app_title"
android:theme="@android:style/Theme.Holo.Light" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.kaytat.simpleprotocolplayer.NoticeActivity"
android:label="@string/notice_title"
android:theme="@android:style/Theme.Holo.Light.NoActionBar" >
</activity>
<service
android:name="com.kaytat.simpleprotocolplayer.MusicService"
android:exported="false" >
<intent-filter>
<action android:name="com.kaytat.simpleprotocolplayer.action.PLAY" />
<action android:name="com.kaytat.simpleprotocolplayer.action.STOP" />
</intent-filter>
</service>
<receiver android:name="com.kaytat.simpleprotocolplayer.MusicIntentReceiver" >
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>

199
src/main/assets/notice.txt Normal file
View File

@ -0,0 +1,199 @@
------------------------------------------------------------
This app uses samples from the Android SDK.
Copyright (c) 2005-2008, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
------------------------------------------------------------
Icons
http://www.flaticon.com - designed by Flaticon.com

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.content.Context;
import android.media.AudioManager;
/**
* Convenience class to deal with audio focus. This class deals with everything related to audio
* focus: it can request and abandon focus, and will intercept focus change events and deliver
* them to a MusicFocusable interface (which, in our case, is implemented by {@link MusicService}).
*
* This class can only be used on SDK level 8 and above, since it uses API features that are not
* available on previous SDK's.
*/
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAM;
MusicFocusable mFocusable;
public AudioFocusHelper(Context ctx, MusicFocusable focusable) {
mAM = (AudioManager) ctx.getSystemService(Context.AUDIO_SERVICE);
mFocusable = focusable;
}
/** Requests audio focus. Returns whether request was successful or not. */
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAM.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
/** Abandons audio focus. Returns whether request was successful or not. */
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAM.abandonAudioFocus(this);
}
/**
* Called by AudioManager on audio focus changes. We implement this by calling our
* MusicFocusable appropriately to relay the message.
*/
public void onAudioFocusChange(int focusChange) {
if (mFocusable == null) return;
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
mFocusable.onGainedAudioFocus();
break;
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
mFocusable.onLostAudioFocus(false);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mFocusable.onLostAudioFocus(true);
break;
default:
}
}
}

View File

@ -0,0 +1,459 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Code for NoFilter related from here:
* http://stackoverflow.com/questions/8512762/autocompletetextview-disable-filtering
*/
package com.kaytat.simpleprotocolplayer;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Main activity: shows media player buttons. This activity shows the media player buttons and
* lets the user click them. No media handling is done here -- everything is done by passing
* Intents to our {@link MusicService}.
* */
public class MainActivity extends Activity implements OnClickListener {
private static final String TAG = "MainActivity";
AutoCompleteTextView mIPAddrText;
ArrayList<String> mIPAddrList;
ArrayAdapter<String> mIPAddrAdapter;
AutoCompleteTextView mAudioPortText;
ArrayList<String> mAudioPortList;
ArrayAdapter<String> mAudioPortAdapter;
int mSampleRate;
boolean mStereo;
int mBufferMs;
Button mPlayButton;
Button mStopButton;
/**
* Called when the activity is first created. Here, we simply set the event listeners and
* start the background service ({@link MusicService}) that will handle the actual media
* playback.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mIPAddrText = (AutoCompleteTextView) findViewById(R.id.editTextIpAddr);
mAudioPortText = (AutoCompleteTextView) findViewById(R.id.editTextAudioPort);
mStopButton = (Button) findViewById(R.id.stopbutton);
mPlayButton = (Button) findViewById(R.id.playbutton);
mStopButton = (Button) findViewById(R.id.stopbutton);
mPlayButton.setOnClickListener(this);
mStopButton.setOnClickListener(this);
// Allow full list to be shown on first focus
mIPAddrText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mIPAddrText.showDropDown();
return false;
}
});
mIPAddrText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && mIPAddrText.getAdapter() != null)
mIPAddrText.showDropDown();
}
});
mAudioPortText.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mAudioPortText.showDropDown();
return false;
}
});
mAudioPortText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus && mIPAddrText.getAdapter() != null)
mAudioPortText.showDropDown();
}
});
}
/*
The two different approaches here is an attempt to support both an old preferences
and new preferences. The newer version saved to JSON while the old version just saved
one string.
*/
static final String IP_PREF = "IP_PREF";
static final String PORT_PREF = "PORT_PREF";
static final String IP_JSON_PREF = "IP_JSON_PREF";
static final String PORT_JSON_PREF = "PORT_JSON_PREF";
static final String RATE_PREF = "RATE";
static final String STEREO_PREF = "STEREO";
static final String BUFFER_MS_PREF = "BUFFER_MS";
ArrayList<String> getListFromPrefs(
SharedPreferences prefs,
String keyJson,
String keySingle)
{
// Retrieve the values from the shared preferences
String jsonString = prefs.getString(keyJson, null);
ArrayList<String> arrayList = new ArrayList<String>();
if (jsonString == null || jsonString.isEmpty())
{
// Try to fill with the original key used
String single = prefs.getString(keySingle, null);
if (single != null && !single.isEmpty())
{
arrayList.add(single);
}
}
else
{
try
{
JSONObject jsonObject = new JSONObject(jsonString);
// Note that the array is hard-coded as the element labelled as 'list'
JSONArray jsonArray = jsonObject.getJSONArray("list");
if (jsonArray != null)
{
for (int i = 0; i < jsonArray.length(); i++)
{
String s = (String)jsonArray.get(i);
if (s != null && !s.isEmpty())
{
arrayList.add((String)s);
}
}
}
}
catch (JSONException jsonException)
{
Log.i(TAG, jsonException.toString());
}
}
return arrayList;
}
private ArrayList<String> getUpdatedArrayList(
SharedPreferences prefs,
AutoCompleteTextView view,
String keyJson,
String keySingle) {
// Retrieve the values from the shared preferences
ArrayList<String> arrayList = getListFromPrefs(
prefs,
keyJson,
keySingle);
// Make sure the most recent IP is on top
arrayList.remove(view.getText().toString());
arrayList.add(0, view.getText().toString());
if (arrayList.size() >= 4)
{
arrayList.subList(4, arrayList.size()).clear();
}
return arrayList;
}
private JSONObject getJson(ArrayList<String> arrayList)
{
JSONArray jsonArray = new JSONArray(arrayList);
JSONObject jsonObject = new JSONObject();
try
{
jsonObject.put("list", jsonArray);
}
catch (JSONException jsonException)
{
Log.i(TAG, jsonException.toString());
}
return jsonObject;
}
private void savePrefs()
{
SharedPreferences myPrefs = this.getSharedPreferences("myPrefs", MODE_PRIVATE);
SharedPreferences.Editor prefsEditor = myPrefs.edit();
mIPAddrList = getUpdatedArrayList(myPrefs, mIPAddrText, IP_JSON_PREF, IP_PREF);
mAudioPortList = getUpdatedArrayList(myPrefs, mAudioPortText, PORT_JSON_PREF, PORT_PREF);
// Write out JSON object
prefsEditor.putString(IP_JSON_PREF, getJson(mIPAddrList).toString());
prefsEditor.putString(PORT_JSON_PREF, getJson(mAudioPortList).toString());
prefsEditor.putBoolean(STEREO_PREF, mStereo);
prefsEditor.putInt(RATE_PREF, mSampleRate);
prefsEditor.putInt(BUFFER_MS_PREF, mBufferMs);
prefsEditor.apply();
// Update adapters
mIPAddrAdapter.clear();
mIPAddrAdapter.addAll(mIPAddrList);
mIPAddrAdapter.notifyDataSetChanged();
mAudioPortAdapter.clear();
mAudioPortAdapter.addAll(mAudioPortList);
mAudioPortAdapter.notifyDataSetChanged();
}
@Override
public void onPause() {
super.onPause();
savePrefs();
}
private class NoFilterArrayAdapter<T>
extends ArrayAdapter<T>
{
private Filter filter = new NoFilter();
public List<T> items;
@Override
public Filter getFilter() {
return filter;
}
public NoFilterArrayAdapter(Context context, int textViewResourceId,
List<T> objects) {
super(context, textViewResourceId, objects);
Log.v(TAG, "Adapter created " + filter);
items = objects;
}
private class NoFilter extends Filter {
@Override
protected android.widget.Filter.FilterResults performFiltering(CharSequence arg0) {
android.widget.Filter.FilterResults result = new android.widget.Filter.FilterResults();
result.values = items;
result.count = items.size();
return result;
}
@Override
protected void publishResults(CharSequence arg0, android.widget.Filter.FilterResults arg1) {
notifyDataSetChanged();
}
}
}
@Override
public void onResume()
{
super.onResume();
SharedPreferences myPrefs = this.getSharedPreferences("myPrefs", MODE_PRIVATE);
mIPAddrList = getListFromPrefs(myPrefs, IP_JSON_PREF, IP_PREF);
mIPAddrAdapter = new NoFilterArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mIPAddrList);
mIPAddrText.setAdapter(mIPAddrAdapter);
mIPAddrText.setThreshold(1);
if (mIPAddrList.size() != 0)
{
mIPAddrText.setText((String)mIPAddrList.get(0));
}
mAudioPortList = getListFromPrefs(myPrefs, PORT_JSON_PREF, PORT_PREF);
mAudioPortAdapter = new NoFilterArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mAudioPortList);
mAudioPortText.setAdapter(mAudioPortAdapter);
mAudioPortText.setThreshold(1);
if (mAudioPortList.size() != 0)
{
mAudioPortText.setText((String)mAudioPortList.get(0));
}
// These hard-coded values should match the defaults in the strings array
Resources res = getResources();
mSampleRate = myPrefs.getInt(RATE_PREF, MusicService.DEFAULT_SAMPLE_RATE);
String rateString = Integer.toString(mSampleRate);
String[] sampleRateStrings = res.getStringArray(R.array.sampleRates);
for (int i = 0; i < sampleRateStrings.length; i++) {
if (sampleRateStrings[i].contains(rateString)) {
Spinner sampleRateSpinner = (Spinner) findViewById(R.id.spinnerSampleRate);
sampleRateSpinner.setSelection(i);
break;
}
}
mStereo = myPrefs.getBoolean(STEREO_PREF, MusicService.DEFAULT_STEREO);
String[] stereoStrings = res.getStringArray(R.array.stereo);
Spinner stereoSpinner = (Spinner) findViewById(R.id.stereo);
String stereoKey = getResources().getString(R.string.stereoKey);
if (stereoStrings[0].contains(stereoKey) == mStereo) {
stereoSpinner.setSelection(0);
} else {
stereoSpinner.setSelection(1);
}
mBufferMs = myPrefs.getInt(BUFFER_MS_PREF, 50);
Log.d(TAG, "mBufferMs:" + mBufferMs);
EditText e = (EditText)findViewById(R.id.editTextBufferSize);
e.setText(Integer.toString(mBufferMs));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.actions, menu);
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.notice_item:
Intent intent = new Intent(this, NoticeActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void onClick(View target) {
// Send the correct intent to the MusicService, according to the button that was clicked
if (target == mPlayButton) {
// Get the IP address and port and put it in the intent
Intent i = new Intent(MusicService.ACTION_PLAY);
String ipAddr = mIPAddrText.getText().toString();
String portStr = mAudioPortText.getText().toString();
if (ipAddr == null || ipAddr.equals("")) {
Toast.makeText(getApplicationContext(), "Invalid address", Toast.LENGTH_SHORT).show();
return;
}
if (portStr == null || portStr.equals("")) {
Toast.makeText(getApplicationContext(), "Invalid port", Toast.LENGTH_SHORT).show();
return;
}
i.putExtra(MusicService.DATA_IP_ADDRESS, ipAddr);
int audioPort;
try {
audioPort = Integer.parseInt(portStr);
} catch (NumberFormatException nfe) {
Log.e(TAG, "Invalid port:" + nfe);
Toast.makeText(getApplicationContext(), "Invalid port", Toast.LENGTH_SHORT).show();
return;
}
hideKb();
i.putExtra(MusicService.DATA_AUDIO_PORT, audioPort);
// Extract sample rate
Spinner sampleRateSpinner = (Spinner) findViewById(R.id.spinnerSampleRate);
try {
String rateStr = String.valueOf(sampleRateSpinner.getSelectedItem());
String[] rateSplit = rateStr.split(" ");
if (rateSplit.length != 0) {
mSampleRate = Integer.parseInt(rateSplit[0]);
Log.i(TAG, "rate:" + mSampleRate);
i.putExtra(MusicService.DATA_SAMPLE_RATE, mSampleRate);
}
}
catch (Exception e) {
// Ignore parsing errors. The intent will have a default
}
// Extract stereo/mono setting
Spinner stereoSpinner = (Spinner) findViewById(R.id.stereo);
try {
String stereoSettingString = String.valueOf(stereoSpinner.getSelectedItem());
String[] stereoSplit = stereoSettingString.split(" ");
String stereoKey = getResources().getString(R.string.stereoKey);
if (stereoSplit.length != 0) {
mStereo = stereoSplit[0].contains(stereoKey);
Log.i(TAG, "stereo:" + mStereo);
i.putExtra(MusicService.DATA_STEREO, mStereo);
}
}
catch (Exception e) {
// Ignore parsing errors. The intent will have a default
}
// Get the latest buffer entry
EditText e = (EditText)findViewById(R.id.editTextBufferSize);
String bufferMsString = e.getText().toString();
if (bufferMsString == null || bufferMsString.isEmpty()) {
mBufferMs = 0;
} else {
mBufferMs = Integer.parseInt(bufferMsString);
}
i.putExtra(MusicService.DATA_BUFFER_MS, mBufferMs);
// Save current settings
savePrefs();
startService(i);
}
else if (target == mStopButton) {
hideKb();
startService(new Intent(MusicService.ACTION_STOP));
}
}
private void hideKb() {
InputMethodManager inputManager =
(InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);
View v = getCurrentFocus();
if (v != null) {
inputManager.hideSoftInputFromWindow(v.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
}

View File

@ -0,0 +1,85 @@
package com.kaytat.simpleprotocolplayer;
import android.content.ComponentName;
import android.media.AudioManager;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Class that assists with handling new media button APIs available in API level 8.
*/
public class MediaButtonHelper {
// Backwards compatibility code (methods available as of API Level 8)
private static final String TAG = "MediaButtonHelper";
static {
initializeStaticCompatMethods();
}
static Method sMethodRegisterMediaButtonEventReceiver;
static Method sMethodUnregisterMediaButtonEventReceiver;
static void initializeStaticCompatMethods() {
try {
sMethodRegisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"registerMediaButtonEventReceiver",
new Class[] { ComponentName.class });
sMethodUnregisterMediaButtonEventReceiver = AudioManager.class.getMethod(
"unregisterMediaButtonEventReceiver",
new Class[] { ComponentName.class });
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before API level 8.
}
}
public static void registerMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodRegisterMediaButtonEventReceiver == null)
return;
try {
sMethodRegisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException invoking registerMediaButtonEventReceiver.");
e.printStackTrace();
}
}
@SuppressWarnings("unused")
public static void unregisterMediaButtonEventReceiverCompat(AudioManager audioManager,
ComponentName receiver) {
if (sMethodUnregisterMediaButtonEventReceiver == null)
return;
try {
sMethodUnregisterMediaButtonEventReceiver.invoke(audioManager, receiver);
} catch (InvocationTargetException e) {
// Unpack original exception when possible
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
// Unexpected checked exception; wrap and re-throw
throw new RuntimeException(e);
}
} catch (IllegalAccessException e) {
Log.e(TAG, "IllegalAccessException invoking unregisterMediaButtonEventReceiver.");
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
/**
* Represents something that can react to audio focus events. We implement this instead of just
* using AudioManager.OnAudioFocusChangeListener because that interface is only available in SDK
* level 8 and above, and we want our application to work on previous SDKs.
*/
public interface MusicFocusable {
/** Signals that audio focus was gained. */
public void onGainedAudioFocus();
/**
* Signals that audio focus was lost.
*
* @param canDuck If true, audio can continue in "ducked" mode (low volume). Otherwise, all
* audio must stop.
*/
public void onLostAudioFocus(boolean canDuck);
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.view.KeyEvent;
import android.util.Log;
/**
* Receives broadcasted intents. In particular, we are interested in the
* android.media.AUDIO_BECOMING_NOISY and android.intent.action.MEDIA_BUTTON intents, which is
* broadcast, for example, when the user disconnects the headphones. This class works because we are
* declaring it in a &lt;receiver&gt; tag in AndroidManifest.xml.
*/
public class MusicIntentReceiver extends BroadcastReceiver {
static final String TAG = "MusicIntentReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
Log.i(TAG, "onReceive - headphones disconnected. Stopping");
// send an intent to our MusicService to telling it to pause the audio
context.startService(new Intent(MusicService.ACTION_STOP));
} else if (intent.getAction().equals(Intent.ACTION_MEDIA_BUTTON)) {
KeyEvent keyEvent = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
if (keyEvent.getAction() != KeyEvent.ACTION_DOWN)
return;
switch (keyEvent.getKeyCode()) {
case KeyEvent.KEYCODE_MEDIA_STOP:
Log.i(TAG, "onReceive - media button stop");
context.startService(new Intent(MusicService.ACTION_STOP));
break;
}
}
}
}

View File

@ -0,0 +1,529 @@
/*
* Copyright (C) 2011 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import java.io.DataInputStream;
import java.net.Socket;
/**
* Service that handles media playback. This is the Service through which we perform all the media
* handling in our application.
*/
public class MusicService extends Service implements MusicFocusable {
// The tag we put on debug messages
final static String TAG = "SimpleProtocol";
static final int DEFAULT_AUDIO_PORT = 12345;
static final int DEFAULT_SAMPLE_RATE = 44100;
static final boolean DEFAULT_STEREO = true;
static final int DEFAULT_BUFFER_MS = 50;
// These are the Intent actions that we are prepared to handle. Notice that the fact these
// constants exist in our class is a mere convenience: what really defines the actions our
// service can handle are the <action> tags in the <intent-filters> tag for our service in
// AndroidManifest.xml.
public static final String ACTION_PLAY = "com.kaytat.simpleprotocolplayer.action.PLAY";
public static final String ACTION_STOP = "com.kaytat.simpleprotocolplayer.action.STOP";
public static final String DATA_IP_ADDRESS = "ip_addr";
public static final String DATA_AUDIO_PORT = "audio_port";
public static final String DATA_SAMPLE_RATE = "sample_rate";
public static final String DATA_STEREO = "stereo";
public static final String DATA_BUFFER_MS = "buffer_ms";
// The volume we set the media player to when we lose audio focus, but are allowed to reduce
// the volume instead of stopping playback.
public static final float DUCK_VOLUME = 0.1f;
// Media track
private AudioTrack mTrack = null;
private ThreadStoppable bufferToAudioTrackWorkerThread = null;
private ThreadStoppable networkReadWorkerThread = null;
// our AudioFocusHelper object, if it's available (it's available on SDK level >= 8)
// If not available, this will be null. Always check for null before using!
AudioFocusHelper mAudioFocusHelper = null;
// indicates the state our service:
enum State {
Stopped, // media player is stopped and not prepared to play
Playing // playback active (media player ready!)
}
State mState = State.Stopped;
// do we have audio focus?
// do we have audio focus?
enum AudioFocus {
NoFocusNoDuck, // we don't have audio focus, and can't duck
NoFocusCanDuck, // we don't have focus, but can play at a low volume ("ducking")
Focused // we have full audio focus
}
AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck;
// Wifi lock that we hold when streaming files from the internet, in order to prevent the
// device from shutting off the Wifi radio
WifiLock mWifiLock;
// The ID we use for the notification (the onscreen alert that appears at the notification
// area at the top of the screen as an icon -- and as text as well if the user expands the
// notification area).
final int NOTIFICATION_ID = 1;
Notification mNotification = null;
@Override
public void onCreate() {
Log.i(TAG, "Creating service");
// Create the Wifi lock (this does not acquire the lock, this just creates it)
mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
// create the Audio Focus Helper, if the Audio Focus feature is available (SDK 8 or above)
if (android.os.Build.VERSION.SDK_INT >= 8)
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
else
mAudioFocus = AudioFocus.Focused; // no focus feature, so we always "have" audio focus
}
/**
* Called when we receive an Intent. When we receive an intent sent to us via startService(),
* this is the method that gets called. So here we react appropriately depending on the
* Intent's action, which specifies what is being requested of us.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent.getAction();
if (action.equals(ACTION_PLAY)) {
processPlayRequest(intent);
}
else if (action.equals(ACTION_STOP)) {
processStopRequest();
}
return START_NOT_STICKY; // Means we started the service, but don't want it to
// restart in case it's killed.
}
void processPlayRequest(Intent i) {
if (mState == State.Stopped) {
tryToGetAudioFocus();
playStream(
i.getStringExtra(DATA_IP_ADDRESS),
i.getIntExtra(DATA_AUDIO_PORT, DEFAULT_AUDIO_PORT),
i.getIntExtra(DATA_SAMPLE_RATE, DEFAULT_SAMPLE_RATE),
i.getBooleanExtra(DATA_STEREO, DEFAULT_STEREO),
i.getIntExtra(DATA_BUFFER_MS, DEFAULT_BUFFER_MS));
}
}
void processStopRequest() {
if (mState == State.Playing) {
mState = State.Stopped;
// let go of all resources...
relaxResources();
giveUpAudioFocus();
// service is no longer necessary. Will be started again if needed.
stopSelf();
}
}
void stopAndInterrupt(ThreadStoppable t) {
if (t != null) {
try {
t.customStop();
t.interrupt();
// Do not join since this can take some time. The
// workers should be able to shutdown independently.
// t.join();
}
catch (Exception e) {
Log.e(TAG, "join exception:" + e);
}
}
}
/**
* Releases resources used by the service for playback. This includes the "foreground service"
* status and notification, the wake locks and the AudioTrack
*/
void relaxResources() {
// stop being a foreground service
stopForeground(true);
// we can also release the Wifi lock, if we're holding it
if (mWifiLock.isHeld()) mWifiLock.release();
// Wait for worker thread to stop if running
stopAndInterrupt(bufferToAudioTrackWorkerThread);
stopAndInterrupt(networkReadWorkerThread);
// Make sure to release any resources
if (mTrack != null) {
mTrack = null;
}
}
void tryToGetAudioFocus() {
if (mAudioFocus != AudioFocus.Focused && mAudioFocusHelper != null
&& mAudioFocusHelper.requestFocus())
mAudioFocus = AudioFocus.Focused;
}
void giveUpAudioFocus() {
if (mAudioFocus == AudioFocus.Focused && mAudioFocusHelper != null
&& mAudioFocusHelper.abandonFocus())
mAudioFocus = AudioFocus.NoFocusNoDuck;
}
/**
* Reconfigures AudioTrack according to audio focus settings and starts/restarts it.
*/
void configVolume() {
if (mAudioFocus == AudioFocus.NoFocusNoDuck) {
// If we don't have audio focus and can't duck, we have to pause, even if mState
// is State.Playing. But we stay in the Playing state so that we know we have to resume
// playback once we get the focus back.
if (mState == State.Playing) {
processStopRequest();
}
}
else if (mAudioFocus == AudioFocus.NoFocusCanDuck) {
mTrack.setStereoVolume(DUCK_VOLUME, DUCK_VOLUME); // we'll be relatively quiet
}
else {
mTrack.setStereoVolume(1.0f, 1.0f); // we can be loud
}
}
static private final int NUM_PKTS = 3;
// The amount of data to read from the network before sending to AudioTrack
private int packet_size;
private final Object filledLock = new Object();
private int filled = 0;
private byte[][] byteArray = new byte[NUM_PKTS][];
void initNetworkData(
int sample_rate,
boolean stereo,
int minBuf,
int buffer_ms) {
// Assume 16 bits per sample
int bytesPerSecond = sample_rate * 2;
if (stereo) {
bytesPerSecond *= 2;
}
packet_size = (bytesPerSecond * buffer_ms) / 1000;
if ((packet_size & 1) != 0) {
packet_size++;
}
Log.d(TAG, "initNetworkData:bytes / second:" + (bytesPerSecond));
Log.d(TAG, "initNetworkData:minBuf:" + minBuf);
Log.d(TAG, "initNetworkData:packet_size:" + packet_size);
for (int i = 0; i < NUM_PKTS; i++) {
byteArray[i] = new byte[packet_size];
}
}
private class ThreadStoppable extends Thread {
boolean running = true;
public void customStop() {
running = false;
}
}
/**
* Worker thread that takes data from the buffer and sends it to audio track
*/
private class BufferToAudioTrackThread extends ThreadStoppable {
static final String TAG = "BTATThread";
private AudioTrack mTrack;
public BufferToAudioTrackThread(AudioTrack track) {
mTrack = track;
}
@Override
public void run() {
Log.i(TAG, "start");
mTrack.play();
try {
int idx = 0;
while (running) {
synchronized (filledLock) {
while (filled == 0) {
filledLock.wait();
if (!running) {
throw new Exception("Not running");
}
}
}
mTrack.write(byteArray[idx], 0, packet_size);
idx++;
if (idx == NUM_PKTS) {
idx = 0;
}
synchronized (filledLock) {
filled--;
filledLock.notifyAll();
}
}
}
catch (Exception e) {
Log.e(TAG, "exception:" + e);
}
// Do some cleanup
mTrack.stop();
mTrack.release();
mTrack = null;
Log.i(TAG, "done");
}
}
/**
* Worker thread reads data from the network
*/
private class NetworkReadThread extends ThreadStoppable {
static final String TAG = "NRThread";
Context context;
String ipAddr;
int port;
Socket socket;
// socket timeout at 5 seconds
static final int SOCKET_TIMEOUT = 5 * 1000;
public NetworkReadThread(Context context, String ipAddr, int port) {
this.context = context;
this.ipAddr = ipAddr;
this.port = port;
}
@Override
public void run() {
Log.i(TAG, "start");
byte[] tmpBuf = new byte[packet_size];
try {
// Create the TCP socket and setup some parameters
socket = new Socket(ipAddr, port);
DataInputStream is = new DataInputStream(
socket.getInputStream());
try {
socket.setSoTimeout(SOCKET_TIMEOUT);
socket.setTcpNoDelay(true);
} catch (Exception e) {
Log.i(TAG, "exception:" + e);
return;
}
Log.i(TAG, "running");
int idx = 0;
while (running) {
synchronized (filledLock) {
while (filled == NUM_PKTS) {
filledLock.wait();
if (!running) {
throw new Exception("Not running");
}
}
}
// Get a packet
is.readFully(byteArray[idx]);
idx++;
if (idx == NUM_PKTS) {
idx = 0;
}
synchronized (filledLock) {
filled++;
if (filled == NUM_PKTS) {
Log.i(TAG, "flushing");
// Filled up. Throw away everything that's in the network queue.
int rd;
do {
rd = is.read(tmpBuf);
} while (rd == packet_size && running);
}
filledLock.notifyAll();
}
}
}
catch (Exception e) {
Log.i(TAG, "exception:" + e);
}
try {
if (socket != null) {
socket.close();
}
if (running) {
// Broke out of loop unexpectedly. Shutdown.
Handler h = new Handler(context.getMainLooper());
Runnable r = new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "Unable to stream", Toast.LENGTH_SHORT).show();
processStopRequest();
}
};
h.post(r);
}
}
catch (Exception e) {
Log.i(TAG, "exception while closing:" + e);
}
Log.i(TAG, "done");
}
}
/**
* Play the stream using the given IP address and port
*/
void playStream(String serverAddr,
int serverPort,
int sample_rate,
boolean stereo,
int buffer_ms) {
int format = stereo ?
AudioFormat.CHANNEL_OUT_STEREO :
AudioFormat.CHANNEL_OUT_MONO;
// Sanitize input, just in case
if (sample_rate <= 0) {
sample_rate = DEFAULT_SAMPLE_RATE;
}
if (buffer_ms <= 5) {
buffer_ms = DEFAULT_BUFFER_MS;
}
int minBuf = AudioTrack.getMinBufferSize(
sample_rate,
format,
AudioFormat.ENCODING_PCM_16BIT);
initNetworkData(sample_rate, stereo, minBuf, buffer_ms);
mState = State.Stopped;
relaxResources();
filled = 0;
// The agreement here is that mTrack will be shutdown by the helper
mTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sample_rate, format,
AudioFormat.ENCODING_PCM_16BIT, minBuf,
AudioTrack.MODE_STREAM);
bufferToAudioTrackWorkerThread = new BufferToAudioTrackThread(mTrack);
networkReadWorkerThread = new NetworkReadThread(this, serverAddr, serverPort);
bufferToAudioTrackWorkerThread.start();
networkReadWorkerThread.start();
mState = State.Playing;
configVolume();
setUpAsForeground("Streaming from " + serverAddr);
}
/**
* Configures service as a foreground service. A foreground service is a service that's doing
* something the user is actively aware of (such as playing music), and must appear to the
* user as a notification. That's why we create the notification here.
*/
void setUpAsForeground(String text) {
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
mNotification = new Notification();
mNotification.tickerText = text;
mNotification.icon = R.drawable.ic_stat_playing;
mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
mNotification.setLatestEventInfo(getApplicationContext(), "SimpleProtocolPlayer",
text, pi);
startForeground(NOTIFICATION_ID, mNotification);
}
@Override
public void onGainedAudioFocus() {
Log.i(TAG, "Gained audio focus");
mAudioFocus = AudioFocus.Focused;
// restart media player with new focus settings
if (mState == State.Playing)
configVolume();
}
@Override
public void onLostAudioFocus(boolean canDuck) {
Log.i(TAG, "Lost audio focus: canDuck:" + canDuck);
mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck;
// start/restart/pause media player with new focus settings
if (mState == State.Playing)
configVolume();
}
@Override
public void onDestroy() {
// Service is being killed, so make sure we release our resources
mState = State.Stopped;
relaxResources();
giveUpAudioFocus();
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2007 The Android Open Source Project
* Copyright (C) 2014 kaytat
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.io.InputStream;
import java.io.IOException;
public class NoticeActivity extends Activity
{
static final String TAG = "NoticeActivity";
/**
* Initialization of the Activity after it is first created. Must at least
* call {@link android.app.Activity#setContentView setContentView()} to
* describe what is to be displayed in the screen.
*/
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// See assets/res/any/layout/styled_text.xml for this
// view layout definition.
setContentView(R.layout.notice);
// Programmatically load text from an asset and place it into the
// text view. Note that the text we are loading is ASCII, so we
// need to convert it to UTF-16.
try {
InputStream is = getAssets().open("notice.txt");
// We guarantee that the available method returns the total
// size of the asset... of course, this does mean that a single
// asset can't be more than 2 gigs.
int size = is.available();
// Read the entire asset into a local byte buffer.
byte[] buffer = new byte[size];
int readBytes = is.read(buffer);
is.close();
// Finally stick the string into the text view.
TextView tv = (TextView)findViewById(R.id.notice_view);
if (readBytes != 0) {
tv.setText(new String(buffer));
}
else {
tv.setText("Error");
}
} catch (IOException e) {
// Should never happen!
Log.e(TAG, "exception:" + e);
}
}
}

View File

@ -0,0 +1,353 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.app.PendingIntent;
import android.graphics.Bitmap;
import android.os.Looper;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* RemoteControlClient enables exposing information meant to be consumed by remote controls capable
* of displaying metadata, artwork and media transport control buttons. A remote control client
* object is associated with a media button event receiver. This event receiver must have been
* previously registered with
* {@link android.media.AudioManager#registerMediaButtonEventReceiver(android.content.ComponentName)}
* before the RemoteControlClient can be registered through
* {@link android.media.AudioManager#registerRemoteControlClient(android.media.RemoteControlClient)}.
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class RemoteControlClientCompat {
private static final String TAG = "RemoteControlCompat";
private static Class sRemoteControlClientClass;
// RCC short for RemoteControlClient
private static Method sRCCEditMetadataMethod;
private static Method sRCCSetPlayStateMethod;
private static Method sRCCSetTransportControlFlags;
private static boolean sHasRemoteControlAPIs = false;
static {
try {
ClassLoader classLoader = RemoteControlClientCompat.class.getClassLoader();
sRemoteControlClientClass = getActualRemoteControlClientClass(classLoader);
// dynamically populate the playstate and flag values in case they change
// in future versions.
for (Field field : RemoteControlClientCompat.class.getFields()) {
try {
Field realField = sRemoteControlClientClass.getField(field.getName());
Object realValue = realField.get(null);
field.set(null, realValue);
} catch (NoSuchFieldException e) {
Log.w(TAG, "Could not get real field: " + field.getName());
} catch (IllegalArgumentException e) {
Log.w(TAG, "Error trying to pull field value for: " + field.getName()
+ " " + e.getMessage());
} catch (IllegalAccessException e) {
Log.w(TAG, "Error trying to pull field value for: " + field.getName()
+ " " + e.getMessage());
}
}
// get the required public methods on RemoteControlClient
sRCCEditMetadataMethod = sRemoteControlClientClass.getMethod("editMetadata",
boolean.class);
sRCCSetPlayStateMethod = sRemoteControlClientClass.getMethod("setPlaybackState",
int.class);
sRCCSetTransportControlFlags = sRemoteControlClientClass.getMethod(
"setTransportControlFlags", int.class);
sHasRemoteControlAPIs = true;
} catch (ClassNotFoundException e) {
// Silently fail when running on an OS before ICS.
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before ICS.
} catch (IllegalArgumentException e) {
// Silently fail when running on an OS before ICS.
} catch (SecurityException e) {
// Silently fail when running on an OS before ICS.
}
}
public static Class getActualRemoteControlClientClass(ClassLoader classLoader)
throws ClassNotFoundException {
return classLoader.loadClass("android.media.RemoteControlClient");
}
private Object mActualRemoteControlClient;
public RemoteControlClientCompat(PendingIntent pendingIntent) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
mActualRemoteControlClient =
sRemoteControlClientClass.getConstructor(PendingIntent.class)
.newInstance(pendingIntent);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public RemoteControlClientCompat(PendingIntent pendingIntent, Looper looper) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
mActualRemoteControlClient =
sRemoteControlClientClass.getConstructor(PendingIntent.class, Looper.class)
.newInstance(pendingIntent, looper);
} catch (Exception e) {
Log.e(TAG, "Error creating new instance of " + sRemoteControlClientClass.getName(), e);
}
}
/**
* Class used to modify metadata in a {@link android.media.RemoteControlClient} object. Use
* {@link android.media.RemoteControlClient#editMetadata(boolean)} to create an instance of an
* editor, on which you set the metadata for the RemoteControlClient instance. Once all the
* information has been set, use {@link #apply()} to make it the new metadata that should be
* displayed for the associated client. Once the metadata has been "applied", you cannot reuse
* this instance of the MetadataEditor.
*/
public class MetadataEditorCompat {
private Method mPutStringMethod;
private Method mPutBitmapMethod;
private Method mPutLongMethod;
private Method mClearMethod;
private Method mApplyMethod;
private Object mActualMetadataEditor;
/**
* The metadata key for the content artwork / album art.
*/
public final static int METADATA_KEY_ARTWORK = 100;
private MetadataEditorCompat(Object actualMetadataEditor) {
if (sHasRemoteControlAPIs && actualMetadataEditor == null) {
throw new IllegalArgumentException("Remote Control API's exist, " +
"should not be given a null MetadataEditor");
}
if (sHasRemoteControlAPIs) {
Class metadataEditorClass = actualMetadataEditor.getClass();
try {
mPutStringMethod = metadataEditorClass.getMethod("putString",
int.class, String.class);
mPutBitmapMethod = metadataEditorClass.getMethod("putBitmap",
int.class, Bitmap.class);
mPutLongMethod = metadataEditorClass.getMethod("putLong",
int.class, long.class);
mClearMethod = metadataEditorClass.getMethod("clear", new Class[]{});
mApplyMethod = metadataEditorClass.getMethod("apply", new Class[]{});
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
mActualMetadataEditor = actualMetadataEditor;
}
/**
* Adds textual information to be displayed.
* Note that none of the information added after {@link #apply()} has been called,
* will be displayed.
* @param key The identifier of a the metadata field to set. Valid values are
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUM},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ALBUMARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_ARTIST},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_AUTHOR},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPILATION},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_COMPOSER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DATE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_GENRE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_TITLE},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_WRITER}.
* @param value The text for the given key, or {@code null} to signify there is no valid
* information for the field.
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
*/
public MetadataEditorCompat putString(int key, String value) {
if (sHasRemoteControlAPIs) {
try {
mPutStringMethod.invoke(mActualMetadataEditor, key, value);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Sets the album / artwork picture to be displayed on the remote control.
* @param key the identifier of the bitmap to set. The only valid value is
* {@link #METADATA_KEY_ARTWORK}
* @param bitmap The bitmap for the artwork, or null if there isn't any.
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
* @throws IllegalArgumentException
* @see android.graphics.Bitmap
*/
public MetadataEditorCompat putBitmap(int key, Bitmap bitmap) {
if (sHasRemoteControlAPIs) {
try {
mPutBitmapMethod.invoke(mActualMetadataEditor, key, bitmap);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Adds numerical information to be displayed.
* Note that none of the information added after {@link #apply()} has been called,
* will be displayed.
* @param key the identifier of a the metadata field to set. Valid values are
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_CD_TRACK_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DISC_NUMBER},
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} (with a value
* expressed in milliseconds),
* {@link android.media.MediaMetadataRetriever#METADATA_KEY_YEAR}.
* @param value The long value for the given key
* @return Returns a reference to the same MetadataEditor object, so you can chain put
* calls together.
* @throws IllegalArgumentException
*/
public MetadataEditorCompat putLong(int key, long value) {
if (sHasRemoteControlAPIs) {
try {
mPutLongMethod.invoke(mActualMetadataEditor, key, value);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
return this;
}
/**
* Clears all the metadata that has been set since the MetadataEditor instance was
* created with {@link android.media.RemoteControlClient#editMetadata(boolean)}.
*/
public void clear() {
if (sHasRemoteControlAPIs) {
try {
mClearMethod.invoke(mActualMetadataEditor, (Object[]) null);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* Associates all the metadata that has been set since the MetadataEditor instance was
* created with {@link android.media.RemoteControlClient#editMetadata(boolean)}, or since
* {@link #clear()} was called, with the RemoteControlClient. Once "applied", this
* MetadataEditor cannot be reused to edit the RemoteControlClient's metadata.
*/
public void apply() {
if (sHasRemoteControlAPIs) {
try {
mApplyMethod.invoke(mActualMetadataEditor, (Object[]) null);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
/**
* Creates a {@link android.media.RemoteControlClient.MetadataEditor}.
* @param startEmpty Set to false if you want the MetadataEditor to contain the metadata that
* was previously applied to the RemoteControlClient, or true if it is to be created empty.
* @return a new MetadataEditor instance.
*/
public MetadataEditorCompat editMetadata(boolean startEmpty) {
Object metadataEditor;
if (sHasRemoteControlAPIs) {
try {
metadataEditor = sRCCEditMetadataMethod.invoke(mActualRemoteControlClient,
startEmpty);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
metadataEditor = null;
}
return new MetadataEditorCompat(metadataEditor);
}
/**
* Sets the current playback state.
* @param state The current playback state, one of the following values:
* {@link android.media.RemoteControlClient#PLAYSTATE_STOPPED},
* {@link android.media.RemoteControlClient#PLAYSTATE_PAUSED},
* {@link android.media.RemoteControlClient#PLAYSTATE_PLAYING},
* {@link android.media.RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
* {@link android.media.RemoteControlClient#PLAYSTATE_REWINDING},
* {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
* {@link android.media.RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
* {@link android.media.RemoteControlClient#PLAYSTATE_BUFFERING},
* {@link android.media.RemoteControlClient#PLAYSTATE_ERROR}.
*/
public void setPlaybackState(int state) {
if (sHasRemoteControlAPIs) {
try {
sRCCSetPlayStateMethod.invoke(mActualRemoteControlClient, state);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Sets the flags for the media transport control buttons that this client supports.
* @param transportControlFlags A combination of the following flags:
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PREVIOUS},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_REWIND},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PLAY_PAUSE},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_PAUSE},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_STOP},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_FAST_FORWARD},
* {@link android.media.RemoteControlClient#FLAG_KEY_MEDIA_NEXT}
*/
public void setTransportControlFlags(int transportControlFlags) {
if (sHasRemoteControlAPIs) {
try {
sRCCSetTransportControlFlags.invoke(mActualRemoteControlClient,
transportControlFlags);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public final Object getActualRemoteControlClientObject() {
return mActualRemoteControlClient;
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kaytat.simpleprotocolplayer;
import android.media.AudioManager;
import android.util.Log;
import java.lang.reflect.Method;
/**
* Contains methods to handle registering/unregistering remote control clients. These methods only
* run on ICS devices. On previous devices, all methods are no-ops.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class RemoteControlHelper {
private static final String TAG = "RemoteControlHelper";
private static boolean sHasRemoteControlAPIs = false;
private static Method sRegisterRemoteControlClientMethod;
private static Method sUnregisterRemoteControlClientMethod;
static {
try {
ClassLoader classLoader = RemoteControlHelper.class.getClassLoader();
Class sRemoteControlClientClass =
RemoteControlClientCompat.getActualRemoteControlClientClass(classLoader);
sRegisterRemoteControlClientMethod = AudioManager.class.getMethod(
"registerRemoteControlClient", new Class[]{sRemoteControlClientClass});
sUnregisterRemoteControlClientMethod = AudioManager.class.getMethod(
"unregisterRemoteControlClient", new Class[]{sRemoteControlClientClass});
sHasRemoteControlAPIs = true;
} catch (ClassNotFoundException e) {
// Silently fail when running on an OS before ICS.
} catch (NoSuchMethodException e) {
// Silently fail when running on an OS before ICS.
} catch (IllegalArgumentException e) {
// Silently fail when running on an OS before ICS.
} catch (SecurityException e) {
// Silently fail when running on an OS before ICS.
}
}
public static void registerRemoteControlClient(AudioManager audioManager,
RemoteControlClientCompat remoteControlClient) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
sRegisterRemoteControlClientMethod.invoke(audioManager,
remoteControlClient.getActualRemoteControlClientObject());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
public static void unregisterRemoteControlClient(AudioManager audioManager,
RemoteControlClientCompat remoteControlClient) {
if (!sHasRemoteControlAPIs) {
return;
}
try {
sUnregisterRemoteControlClientMethod.invoke(audioManager,
remoteControlClient.getActualRemoteControlClientObject());
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/eject_pressed" />
<item android:state_focused="true" android:drawable="@drawable/eject_pressed" />
<item android:drawable="@drawable/eject" />
</selector>

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/ff_pressed" />
<item android:state_focused="true" android:drawable="@drawable/ff_pressed" />
<item android:drawable="@drawable/ff" />
</selector>

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/pause_pressed" />
<item android:state_focused="true" android:drawable="@drawable/pause_pressed" />
<item android:drawable="@drawable/pause" />
</selector>

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/play_pressed" />
<item android:state_focused="true" android:drawable="@drawable/play_pressed" />
<item android:drawable="@drawable/play" />
</selector>

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/rew_pressed" />
<item android:state_focused="true" android:drawable="@drawable/rew_pressed" />
<item android:drawable="@drawable/rew" />
</selector>

View File

@ -0,0 +1,21 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/stop_pressed" />
<item android:state_focused="true" android:drawable="@drawable/stop_pressed" />
<item android:drawable="@drawable/stop" />
</selector>

View File

@ -0,0 +1,123 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<View
android:id="@+id/anchor"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/labelIpAddr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/editTextIpAddr"
android:layout_toLeftOf="@+id/editTextIpAddr"
android:text="@string/labelIpAddr" />
<AutoCompleteTextView
android:id="@+id/editTextIpAddr"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/anchor"
android:layout_alignLeft="@+id/anchor"
android:layout_marginRight="40dp"
android:inputType="text" />
<TextView
android:id="@+id/labelAudioPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/editTextAudioPort"
android:layout_toLeftOf="@+id/editTextAudioPort"
android:text="@string/labelAudioPort" />
<AutoCompleteTextView
android:id="@+id/editTextAudioPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/editTextIpAddr"
android:layout_alignRight="@+id/editTextIpAddr"
android:layout_below="@+id/editTextIpAddr"
android:inputType="number"
android:text="@string/defaultAudioPort" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<View
android:id="@+id/anchor2"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_centerInParent="true" />
<TextView
android:id="@+id/labelSampleRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/spinnerSampleRate"
android:text="@string/labelSampleRate" />
<Spinner
android:id="@+id/spinnerSampleRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/anchor2"
android:entries="@+array/sampleRates" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<Button
android:id="@+id/playbutton"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
android:background="@drawable/btn_play"
style="?android:attr/borderlessButtonStyle" />
<Button
android:id="@+id/stopbutton"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
android:background="@drawable/btn_stop"
style="?android:attr/borderlessButtonStyle" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,193 @@
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:orientation="vertical">
<RelativeLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/center"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:id="@+id/labelIpAddr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/center"
android:layout_centerVertical="true"
android:text="@string/labelIpAddr" />
<AutoCompleteTextView
android:id="@+id/editTextIpAddr"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/center"
android:layout_alignParentRight="true"
android:inputType="text" />
</RelativeLayout>
<RelativeLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/center2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:id="@+id/labelAudioPort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/center2"
android:text="@string/labelAudioPort" />
<AutoCompleteTextView
android:id="@+id/editTextAudioPort"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/center2"
android:layout_alignParentRight="true"
android:inputType="number"
android:text="@string/defaultAudioPort" />
</RelativeLayout>
<RelativeLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/center3"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:id="@+id/labelSampleRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/center3"
android:layout_centerVertical="true"
android:text="@string/labelSampleRate" />
<Spinner
android:id="@+id/spinnerSampleRate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/center3"
android:entries="@array/sampleRates" />
</RelativeLayout>
<RelativeLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/center4"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:id="@+id/labelStereo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/center4"
android:layout_centerVertical="true"
android:text="@string/labelStereoMono" />
<Spinner
android:id="@+id/stereo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/center4"
android:entries="@array/stereo" />
</RelativeLayout>
<RelativeLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/center5"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_centerHorizontal="true"
android:visibility="invisible"/>
<TextView
android:id="@+id/labelBufferSize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="@+id/center5"
android:layout_centerVertical="true"
android:text="@string/bufferSize" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:ems="10"
android:id="@+id/editTextBufferSize"
android:layout_toRightOf="@+id/center5"
android:layout_gravity="center_horizontal" />
</RelativeLayout>
<LinearLayout
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/playbutton"
style="?android:attr/borderlessButtonStyle"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
android:background="@drawable/btn_play" />
<Button
android:id="@+id/stopbutton"
style="?android:attr/borderlessButtonStyle"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="5dp"
android:background="@drawable/btn_stop" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Demonstrates styled string resources.
See corresponding Java code com.android.sdk.content.StyledText -->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical">
<TextView android:id="@+id/notice_view"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:scrollbars = "vertical"
android:textStyle="normal"/>
</ScrollView>

View File

@ -0,0 +1,4 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/notice_item"
android:title="@string/notice_item"/>
</menu>

View File

@ -0,0 +1,10 @@
<resources>
<!--
Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively).
-->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -0,0 +1,7 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_title">Simple Protocol Player</string>
<string name="labelIpAddr">IP Address</string>
<string name="labelAudioPort">Audio Port</string>
<string name="defaultAudioPort">12345</string>
<string name="notice_item">Notice</string>
<string name="notice_title">Notice</string>
<string name="labelSampleRate">Sample Rate</string>
<string name="labelStereoMono">Mono/Stereo</string>
<string-array name="sampleRates">
<item>11025</item>
<item>12000</item>
<item>22050</item>
<item>24000</item>
<item>44100 (Default)</item>
<item>48000</item>
</string-array>
<string-array name="stereo">
<item>Mono</item>
<item>Stereo (Default)</item>
</string-array>
<!-- used for searching stereo spinner -->
<string name="stereoKey">tereo</string>
<string name="bufferSize">Buffer size (in ms)</string>
</resources>