Merge branch 'develop' ... latest Linux, Android, and Mac variant
|
@ -43,16 +43,19 @@ ylwrap
|
|||
apple2ix*.tar.gz
|
||||
test-driver
|
||||
|
||||
# GDB
|
||||
.gdb_history
|
||||
|
||||
# generated sources
|
||||
src/rom.c
|
||||
src/x86/glue.S
|
||||
src/meta/debug.c
|
||||
src/arm/glue.S
|
||||
|
||||
# sub{tree,module}
|
||||
src/rom
|
||||
|
||||
# generated binaries
|
||||
apple2ix
|
||||
/apple2ix
|
||||
genfont
|
||||
genrom
|
||||
|
||||
|
@ -64,3 +67,22 @@ man6
|
|||
xcuserdata
|
||||
.DS_Store
|
||||
xcshareddata
|
||||
|
||||
# Android CLI builds
|
||||
Android/bin
|
||||
Android/gen
|
||||
Android/libs
|
||||
# Android.mk is tEh dynamicz!
|
||||
Android/jni/Android.mk
|
||||
*.apk
|
||||
|
||||
# Android Studio
|
||||
.gradle
|
||||
Android/local.properties
|
||||
Android/.idea/workspace.xml
|
||||
Android/.idea/libraries
|
||||
Android/.idea/dictionaries
|
||||
Android/build
|
||||
Android/jni/obj
|
||||
Android/obj
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
|
@ -0,0 +1 @@
|
|||
Android
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<resourceExtensions />
|
||||
<wildcardResourcePatterns>
|
||||
<entry name="!?*.java" />
|
||||
<entry name="!?*.form" />
|
||||
<entry name="!?*.class" />
|
||||
<entry name="!?*.groovy" />
|
||||
<entry name="!?*.scala" />
|
||||
<entry name="!?*.flex" />
|
||||
<entry name="!?*.kt" />
|
||||
<entry name="!?*.clj" />
|
||||
<entry name="!?*.aj" />
|
||||
</wildcardResourcePatterns>
|
||||
<annotationProcessing>
|
||||
<profile default="true" name="Default" enabled="false">
|
||||
<processorPath useClasspath="true" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<component name="CopyrightManager">
|
||||
<copyright>
|
||||
<option name="notice" value="Copyright (c) deadc0de.org" />
|
||||
<option name="keyword" value="Copyright" />
|
||||
<option name="allowReplaceKeyword" value="" />
|
||||
<option name="myName" value="deadc0de.org" />
|
||||
<option name="myLocal" value="true" />
|
||||
</copyright>
|
||||
</component>
|
|
@ -0,0 +1,3 @@
|
|||
<component name="CopyrightManager">
|
||||
<settings default="" />
|
||||
</component>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="$APPLICATION_HOME_DIR$/gradle/gradle-2.4" />
|
||||
<option name="gradleJvm" value="1.7" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EntryPointsManager">
|
||||
<entry_points version="2.0" />
|
||||
</component>
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
|
||||
<OptionsSetting value="true" id="Add" />
|
||||
<OptionsSetting value="true" id="Remove" />
|
||||
<OptionsSetting value="true" id="Checkout" />
|
||||
<OptionsSetting value="true" id="Update" />
|
||||
<OptionsSetting value="true" id="Status" />
|
||||
<OptionsSetting value="true" id="Edit" />
|
||||
<ConfirmationsSetting value="0" id="Add" />
|
||||
<ConfirmationsSetting value="0" id="Remove" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/Android.iml" filepath="$PROJECT_DIR$/Android.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="Android" 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="java-gradle" name="Java-Gradle">
|
||||
<configuration>
|
||||
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
|
||||
<option name="BUILDABLE" value="false" />
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1 @@
|
|||
app/build/intermediates/manifests/full/debug/AndroidManifest.xml
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Android" 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=":app" />
|
||||
</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_7" 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/resValues/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/resValues/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/exploded-aar/com.android.support/appcompat-v7/23.1.0/jars" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.0/jars" />
|
||||
<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 23 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" exported="" name="support-annotations-23.1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="appcompat-v7-23.1.0" level="project" />
|
||||
<orderEntry type="library" exported="" name="support-v4-23.1.0" level="project" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,41 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "21.1.2"
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file("release2.keystore")
|
||||
storePassword System.getenv("GOOGSTOREPWD")
|
||||
keyPassword System.getenv("GOOGKEYPWD")
|
||||
keyAlias "release"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
debug {
|
||||
debuggable true
|
||||
jniDebuggable true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.deadc0de.apple2ix.basic"
|
||||
minSdkVersion 10
|
||||
targetSdkVersion 23
|
||||
versionCode 7
|
||||
versionName "1.0.4"
|
||||
ndk {
|
||||
moduleName "apple2ix"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:23.1.0'
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /home/asc/Android/Sdk/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
|
@ -0,0 +1,13 @@
|
|||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.deadc0de.apple2ix.basic" >
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.deadc0de.apple2ix.basic" >
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:resizeable="true"
|
||||
android:smallScreens="false" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name" >
|
||||
|
||||
<activity
|
||||
android:name="org.deadc0de.apple2ix.Apple2Activity"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:windowSoftInputMode="adjustResize" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.nib\\.gz" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.dsk\\.gz" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.do\\.gz" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/x-gzip" android:pathPattern="/.*\\.po\\.gz" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="file" /> <!-- catch-all since I can't get the following to work because ... Android -->
|
||||
<!--
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.nib" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.dsk" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.do" />
|
||||
<data android:scheme="file" android:host="*" android:mimeType="application/octet-stream" android:pathPattern="/.*\\.po" />
|
||||
-->
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1 @@
|
|||
../../../assets
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public abstract class Apple2AbstractMenu implements Apple2MenuView {
|
||||
|
||||
private final static String TAG = "Apple2AbstractMenu";
|
||||
|
||||
protected Apple2Activity mActivity = null;
|
||||
private View mSettingsView = null;
|
||||
|
||||
public Apple2AbstractMenu(Apple2Activity activity) {
|
||||
mActivity = activity;
|
||||
setup();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
mActivity.pushApple2View(this);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
mActivity.popApple2View(this);
|
||||
}
|
||||
|
||||
public void dismissAll() {
|
||||
this.dismiss();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mSettingsView.getParent() != null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mSettingsView;
|
||||
}
|
||||
|
||||
public boolean isCalibrating() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// required overrides ...
|
||||
|
||||
public interface IMenuEnum {
|
||||
public String getTitle(final Apple2Activity activity);
|
||||
|
||||
public String getSummary(final Apple2Activity activity);
|
||||
|
||||
public View getView(final Apple2Activity activity, View convertView);
|
||||
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked);
|
||||
}
|
||||
|
||||
public abstract IMenuEnum[] allValues();
|
||||
|
||||
public abstract String[] allTitles();
|
||||
|
||||
public abstract boolean areAllItemsEnabled();
|
||||
|
||||
public abstract boolean isEnabled(int position);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// boilerplate menu view code
|
||||
|
||||
protected static View _basicView(Apple2Activity activity, IMenuEnum setting, View convertView) {
|
||||
TextView tv = (TextView) convertView.findViewById(R.id.a2preference_title);
|
||||
if (tv == null) {
|
||||
// attemping to recycle different layout ...
|
||||
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
convertView = inflater.inflate(R.layout.a2preference, null, false);
|
||||
tv = (TextView) convertView.findViewById(R.id.a2preference_title);
|
||||
}
|
||||
tv.setText(setting.getTitle(activity));
|
||||
|
||||
tv = (TextView) convertView.findViewById(R.id.a2preference_summary);
|
||||
tv.setText(setting.getSummary(activity));
|
||||
|
||||
LinearLayout layout = (LinearLayout) convertView.findViewById(R.id.a2preference_widget_frame);
|
||||
if (layout.getChildCount() > 0) {
|
||||
// layout cells appear to be reused when scrolling into view ... make sure we start with clear hierarchy
|
||||
layout.removeAllViews();
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public interface IPreferenceLoadSave {
|
||||
public int intValue();
|
||||
|
||||
public void saveInt(int value);
|
||||
}
|
||||
|
||||
public interface IPreferenceSlider extends IPreferenceLoadSave {
|
||||
public void showValue(int value, final TextView seekBarValue);
|
||||
}
|
||||
|
||||
protected static View _sliderView(final Apple2Activity activity, final IMenuEnum setting, final int numChoices, final IPreferenceSlider iLoadSave) {
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View view = inflater.inflate(R.layout.a2preference_slider, null, false);
|
||||
|
||||
TextView tv = (TextView) view.findViewById(R.id.a2preference_slider_summary);
|
||||
tv.setText(setting.getSummary(activity));
|
||||
|
||||
final TextView seekBarValue = (TextView) view.findViewById(R.id.a2preference_slider_seekBarValue);
|
||||
|
||||
SeekBar sb = (SeekBar) view.findViewById(R.id.a2preference_slider_seekBar);
|
||||
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
return;
|
||||
}
|
||||
iLoadSave.showValue(progress, seekBarValue);
|
||||
iLoadSave.saveInt(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
sb.setMax(0); // http://stackoverflow.com/questions/10278467/seekbar-not-setting-actual-progress-setprogress-not-working-on-early-android
|
||||
sb.setMax(numChoices);
|
||||
int progress = iLoadSave.intValue();
|
||||
sb.setProgress(progress);
|
||||
iLoadSave.showValue(progress, seekBarValue);
|
||||
return view;
|
||||
}
|
||||
|
||||
protected static void _alertDialogHandleSelection(final Apple2Activity activity, final int titleId, final String[] choices, final IPreferenceLoadSave iLoadSave) {
|
||||
_alertDialogHandleSelection(activity, activity.getResources().getString(titleId), choices, iLoadSave);
|
||||
}
|
||||
|
||||
protected static void _alertDialogHandleSelection(final Apple2Activity activity, final String titleId, final String[] choices, final IPreferenceLoadSave iLoadSave) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(titleId);
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
final int checkedPosition = iLoadSave.intValue();
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(activity, android.R.layout.select_dialog_singlechoice, choices) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
CheckedTextView ctv = (CheckedTextView) view.findViewById(android.R.id.text1);
|
||||
ctv.setChecked(position == checkedPosition);
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int value) {
|
||||
iLoadSave.saveInt(value);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
activity.registerAndShowDialog(dialog);
|
||||
}
|
||||
|
||||
protected static ImageView _addPopupIcon(Apple2Activity activity, IMenuEnum setting, View convertView) {
|
||||
ImageView imageView = new ImageView(activity);
|
||||
Drawable drawable = activity.getResources().getDrawable(android.R.drawable.ic_menu_edit);
|
||||
imageView.setImageDrawable(drawable);
|
||||
LinearLayout layout = (LinearLayout) convertView.findViewById(R.id.a2preference_widget_frame);
|
||||
layout.addView(imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
protected static CheckBox _addCheckbox(Apple2Activity activity, IMenuEnum setting, View convertView, boolean isChecked) {
|
||||
CheckBox checkBox = new CheckBox(activity);
|
||||
checkBox.setChecked(isChecked);
|
||||
LinearLayout layout = (LinearLayout) convertView.findViewById(R.id.a2preference_widget_frame);
|
||||
layout.addView(checkBox);
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mSettingsView = inflater.inflate(R.layout.activity_settings, null, false);
|
||||
|
||||
final Button cancelButton = (Button) mSettingsView.findViewById(R.id.cancelButton);
|
||||
cancelButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mActivity.dismissAllMenus();
|
||||
}
|
||||
});
|
||||
|
||||
ListView settingsList = (ListView) mSettingsView.findViewById(R.id.listView_settings);
|
||||
settingsList.setEnabled(true);
|
||||
|
||||
ArrayAdapter<?> adapter = new ArrayAdapter<String>(mActivity, R.layout.a2preference, R.id.a2preference_title, allTitles()) {
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return Apple2AbstractMenu.this.areAllItemsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return Apple2AbstractMenu.this.isEnabled(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
//View view = super.getView(position, convertView, parent);
|
||||
// ^^^ WHOA ... this is catching an NPE deep in AOSP code on the second time loading ... WTF?
|
||||
// Methinks it is related to the hack of loading a completely different R.layout.something for certain views...
|
||||
View view = convertView != null ? convertView : super.getView(position, null, parent);
|
||||
IMenuEnum setting = allValues()[position];
|
||||
return setting.getView(mActivity, view);
|
||||
}
|
||||
};
|
||||
|
||||
settingsList.setAdapter(adapter);
|
||||
settingsList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
IMenuEnum setting = allValues()[position];
|
||||
LinearLayout layout = (LinearLayout) view.findViewById(R.id.a2preference_widget_frame);
|
||||
if (layout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
View childView = layout.getChildAt(0);
|
||||
boolean selected = false;
|
||||
if (childView != null && childView instanceof CheckBox) {
|
||||
CheckBox checkBox = (CheckBox) childView;
|
||||
checkBox.setChecked(!checkBox.isChecked());
|
||||
selected = checkBox.isChecked();
|
||||
}
|
||||
setting.handleSelection(mActivity, Apple2AbstractMenu.this, selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,608 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.BuildConfig;
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2Activity extends Activity {
|
||||
|
||||
private final static String TAG = "Apple2Activity";
|
||||
private final static int MAX_FINGERS = 32;// HACK ...
|
||||
private static volatile boolean DEBUG_STRICT = false;
|
||||
|
||||
private Apple2View mView = null;
|
||||
private Runnable mGraphicsInitializedRunnable = null;
|
||||
private Apple2SplashScreen mSplashScreen = null;
|
||||
private Apple2MainMenu mMainMenu = null;
|
||||
private Apple2SettingsMenu mSettingsMenu = null;
|
||||
private Apple2DisksMenu mDisksMenu = null;
|
||||
|
||||
private ArrayList<Apple2MenuView> mMenuStack = new ArrayList<Apple2MenuView>();
|
||||
private ArrayList<AlertDialog> mAlertDialogs = new ArrayList<AlertDialog>();
|
||||
|
||||
private AtomicBoolean mPausing = new AtomicBoolean(false);
|
||||
|
||||
private float[] mXCoords = new float[MAX_FINGERS];
|
||||
private float[] mYCoords = new float[MAX_FINGERS];
|
||||
|
||||
// non-null if we failed to load/link the native code ... likely we are running on some bizarre 'droid variant
|
||||
private static Throwable sNativeBarfedThrowable = null;
|
||||
private static boolean sNativeBarfed = false;
|
||||
|
||||
static {
|
||||
try {
|
||||
System.loadLibrary("apple2ix");
|
||||
} catch (Throwable barf) {
|
||||
sNativeBarfed = true;
|
||||
sNativeBarfedThrowable = barf;
|
||||
}
|
||||
}
|
||||
|
||||
public final static long NATIVE_TOUCH_HANDLED = (1 << 0);
|
||||
public final static long NATIVE_TOUCH_REQUEST_SHOW_MENU = (1 << 1);
|
||||
|
||||
public final static long NATIVE_TOUCH_KEY_TAP = (1 << 4);
|
||||
public final static long NATIVE_TOUCH_KBD = (1 << 5);
|
||||
public final static long NATIVE_TOUCH_JOY = (1 << 6);
|
||||
public final static long NATIVE_TOUCH_MENU = (1 << 7);
|
||||
public final static long NATIVE_TOUCH_JOY_KPAD = (1 << 8);
|
||||
|
||||
public final static long NATIVE_TOUCH_INPUT_DEVICE_CHANGED = (1 << 16);
|
||||
public final static long NATIVE_TOUCH_CPU_SPEED_DEC = (1 << 17);
|
||||
public final static long NATIVE_TOUCH_CPU_SPEED_INC = (1 << 18);
|
||||
|
||||
public final static long NATIVE_TOUCH_ASCII_SCANCODE_SHIFT = 32;
|
||||
public final static long NATIVE_TOUCH_ASCII_SCANCODE_MASK = 0xFFFFL;
|
||||
public final static long NATIVE_TOUCH_ASCII_MASK = 0xFF00L;
|
||||
public final static long NATIVE_TOUCH_SCANCODE_MASK = 0x00FFL;
|
||||
|
||||
private native void nativeOnCreate(String dataDir, int sampleRate, int monoBufferSize, int stereoBufferSize);
|
||||
|
||||
private native void nativeOnKeyDown(int keyCode, int metaState);
|
||||
|
||||
private native void nativeOnKeyUp(int keyCode, int metaState);
|
||||
|
||||
public native void nativeEmulationResume();
|
||||
|
||||
public native void nativeEmulationPause();
|
||||
|
||||
public native void nativeOnQuit();
|
||||
|
||||
public native long nativeOnTouch(int action, int pointerCount, int pointerIndex, float[] xCoords, float[] yCoords);
|
||||
|
||||
public native void nativeReboot();
|
||||
|
||||
public native void nativeChooseDisk(String path, boolean driveA, boolean readOnly);
|
||||
|
||||
public native void nativeEjectDisk(boolean driveA);
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
if (Apple2Activity.DEBUG_STRICT && BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build());
|
||||
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
/*.detectLeakedClosableObjects()*/
|
||||
.penaltyLog()
|
||||
.penaltyDeath()
|
||||
.build());
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.e(TAG, "onCreate()");
|
||||
|
||||
// placeholder view on initial launch
|
||||
if (mView == null) {
|
||||
setContentView(new View(this));
|
||||
}
|
||||
|
||||
Apple2CrashHandler.getInstance().initializeAndSetCustomExceptionHandler(this);
|
||||
if (sNativeBarfed) {
|
||||
Log.e(TAG, "NATIVE BARFED...", sNativeBarfedThrowable);
|
||||
return;
|
||||
}
|
||||
|
||||
int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(this);
|
||||
int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(this, /*isStereo:*/false);
|
||||
int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(this, /*isStereo:*/true);
|
||||
Log.d(TAG, "Device sampleRate:" + sampleRate + " mono bufferSize:" + monoBufferSize + " stereo bufferSize:" + stereoBufferSize);
|
||||
|
||||
String dataDir = Apple2DisksMenu.getDataDir(this);
|
||||
nativeOnCreate(dataDir, sampleRate, monoBufferSize, stereoBufferSize);
|
||||
|
||||
final boolean firstTime = !Apple2Preferences.FIRST_TIME_CONFIGURED.booleanValue(this);
|
||||
Apple2Preferences.FIRST_TIME_CONFIGURED.saveBoolean(this, true);
|
||||
|
||||
showSplashScreen(!firstTime);
|
||||
Apple2CrashHandler.getInstance().checkForCrashes(this);
|
||||
|
||||
mGraphicsInitializedRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (firstTime) {
|
||||
Apple2Preferences.KeypadPreset.IJKM_SPACE.apply(Apple2Activity.this);
|
||||
}
|
||||
Apple2Preferences.loadPreferences(Apple2Activity.this);
|
||||
}
|
||||
};
|
||||
|
||||
// first-time initializations
|
||||
if (firstTime) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Apple2DisksMenu.firstTime(Apple2Activity.this);
|
||||
mSplashScreen.setDismissable(true);
|
||||
Log.d(TAG, "Finished first time copying...");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
mSettingsMenu = new Apple2SettingsMenu(this);
|
||||
mDisksMenu = new Apple2DisksMenu(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
String path = null;
|
||||
if (intent != null) {
|
||||
Uri data = intent.getData();
|
||||
if (data != null) {
|
||||
path = data.getPath();
|
||||
}
|
||||
}
|
||||
if (path != null && Apple2DisksMenu.hasDiskExtension(path)) {
|
||||
handleInsertDiskIntent(path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (sNativeBarfed) {
|
||||
Apple2CrashHandler.getInstance().abandonAllHope(this, sNativeBarfedThrowable);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "onResume()");
|
||||
showSplashScreen(/*dismissable:*/true);
|
||||
Apple2CrashHandler.getInstance().checkForCrashes(this); // NOTE : needs to be called again to clean-up
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (sNativeBarfed) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean wasPausing = mPausing.getAndSet(true);
|
||||
if (wasPausing) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(TAG, "onPause()");
|
||||
if (mView != null) {
|
||||
mView.onPause();
|
||||
}
|
||||
|
||||
// Apparently not good to leave popup/dialog windows showing when backgrounding.
|
||||
// Dismiss these popups to avoid android.view.WindowLeaked issues
|
||||
synchronized (this) {
|
||||
dismissAllMenus();
|
||||
nativeEmulationPause();
|
||||
}
|
||||
|
||||
mPausing.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (sNativeBarfed) {
|
||||
return true;
|
||||
}
|
||||
if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
|
||||
return false;
|
||||
}
|
||||
nativeOnKeyDown(keyCode, event.getMetaState());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (sNativeBarfed) {
|
||||
return true;
|
||||
}
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
Apple2MenuView apple2MenuView = peekApple2View();
|
||||
if (apple2MenuView == null) {
|
||||
showMainMenu();
|
||||
} else {
|
||||
apple2MenuView.dismiss();
|
||||
}
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
showMainMenu();
|
||||
return true;
|
||||
} else if ((keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) || (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) || (keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
|
||||
return false;
|
||||
} else {
|
||||
nativeOnKeyUp(keyCode, event.getMetaState());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
do {
|
||||
|
||||
if (sNativeBarfed) {
|
||||
break;
|
||||
}
|
||||
if (mMainMenu == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Apple2MenuView apple2MenuView = peekApple2View();
|
||||
if ((apple2MenuView != null) && (!apple2MenuView.isCalibrating())) {
|
||||
break;
|
||||
}
|
||||
|
||||
//printSamples(event);
|
||||
int action = event.getActionMasked();
|
||||
int pointerIndex = event.getActionIndex();
|
||||
int pointerCount = event.getPointerCount();
|
||||
for (int i = 0; i < pointerCount/* && i < MAX_FINGERS */; i++) {
|
||||
mXCoords[i] = event.getX(i);
|
||||
mYCoords[i] = event.getY(i);
|
||||
}
|
||||
|
||||
long nativeFlags = nativeOnTouch(action, pointerCount, pointerIndex, mXCoords, mYCoords);
|
||||
|
||||
if ((nativeFlags & NATIVE_TOUCH_HANDLED) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((nativeFlags & NATIVE_TOUCH_REQUEST_SHOW_MENU) != 0) {
|
||||
mMainMenu.show();
|
||||
}
|
||||
|
||||
if ((nativeFlags & NATIVE_TOUCH_KEY_TAP) != 0) {
|
||||
if (Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(this)) {
|
||||
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
|
||||
if (am != null) {
|
||||
am.playSoundEffect(AudioManager.FX_KEY_CLICK);
|
||||
}
|
||||
}
|
||||
|
||||
if ((apple2MenuView != null) && apple2MenuView.isCalibrating()) {
|
||||
long asciiScancodeLong = nativeFlags & (NATIVE_TOUCH_ASCII_SCANCODE_MASK << NATIVE_TOUCH_ASCII_SCANCODE_SHIFT);
|
||||
int asciiInt = (int) (asciiScancodeLong >> (NATIVE_TOUCH_ASCII_SCANCODE_SHIFT + 8));
|
||||
int scancode = (int) ((asciiScancodeLong >> NATIVE_TOUCH_ASCII_SCANCODE_SHIFT) & 0xFFL);
|
||||
char ascii = (char) asciiInt;
|
||||
apple2MenuView.onKeyTapCalibrationEvent(ascii, scancode);
|
||||
}
|
||||
}
|
||||
|
||||
if ((nativeFlags & NATIVE_TOUCH_MENU) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// handle menu-specific actions
|
||||
|
||||
if ((nativeFlags & NATIVE_TOUCH_INPUT_DEVICE_CHANGED) != 0) {
|
||||
Apple2Preferences.TouchDeviceVariant nextVariant;
|
||||
if ((nativeFlags & NATIVE_TOUCH_KBD) != 0) {
|
||||
nextVariant = Apple2Preferences.TouchDeviceVariant.KEYBOARD;
|
||||
} else if ((nativeFlags & NATIVE_TOUCH_JOY) != 0) {
|
||||
nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK;
|
||||
} else if ((nativeFlags & NATIVE_TOUCH_JOY_KPAD) != 0) {
|
||||
nextVariant = Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD;
|
||||
} else {
|
||||
int touchDevice = Apple2Preferences.nativeGetCurrentTouchDevice();
|
||||
nextVariant = Apple2Preferences.TouchDeviceVariant.next(touchDevice);
|
||||
}
|
||||
Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(this, nextVariant);
|
||||
} else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_DEC) != 0) {
|
||||
int percentSpeed = Apple2Preferences.nativeGetCPUSpeed();
|
||||
if (percentSpeed > 400) { // HACK: max value from native side
|
||||
percentSpeed = 375;
|
||||
} else if (percentSpeed > 100) {
|
||||
percentSpeed -= 25;
|
||||
} else {
|
||||
percentSpeed -= 5;
|
||||
}
|
||||
Apple2Preferences.CPU_SPEED_PERCENT.saveInt(this, percentSpeed);
|
||||
} else if ((nativeFlags & NATIVE_TOUCH_CPU_SPEED_INC) != 0) {
|
||||
int percentSpeed = Apple2Preferences.nativeGetCPUSpeed();
|
||||
if (percentSpeed >= 100) {
|
||||
percentSpeed += 25;
|
||||
} else {
|
||||
percentSpeed += 5;
|
||||
}
|
||||
Apple2Preferences.CPU_SPEED_PERCENT.saveInt(this, percentSpeed);
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void showMainMenu() {
|
||||
if (mMainMenu != null) {
|
||||
if (!(mSettingsMenu.isShowing() || mDisksMenu.isShowing())) {
|
||||
mMainMenu.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Apple2MainMenu getMainMenu() {
|
||||
return mMainMenu;
|
||||
}
|
||||
|
||||
public synchronized Apple2DisksMenu getDisksMenu() {
|
||||
return mDisksMenu;
|
||||
}
|
||||
|
||||
public synchronized Apple2SettingsMenu getSettingsMenu() {
|
||||
return mSettingsMenu;
|
||||
}
|
||||
|
||||
private void handleInsertDiskIntent(final String path) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (Apple2Activity.this) {
|
||||
if (mMainMenu == null) {
|
||||
return;
|
||||
}
|
||||
String diskPath = path;
|
||||
File diskFile = new File(diskPath);
|
||||
if (!diskFile.canRead()) {
|
||||
Toast.makeText(Apple2Activity.this, Apple2Activity.this.getString(R.string.disk_insert_could_not_read), Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(Apple2Activity.this, true);
|
||||
final int len = diskPath.length();
|
||||
final String suffix = diskPath.substring(len - 3, len);
|
||||
if (suffix.equalsIgnoreCase(".gz")) { // HACK FIXME TODO : small amount of code duplication of Apple2DisksMenu
|
||||
diskPath = diskPath.substring(0, len - 3);
|
||||
}
|
||||
Apple2Preferences.CURRENT_DISK_A.saveString(Apple2Activity.this, diskPath);
|
||||
|
||||
while (mDisksMenu.popPathStack() != null) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
File storageDir = Apple2DisksMenu.getExternalStorageDirectory();
|
||||
if (storageDir != null) {
|
||||
String storagePath = storageDir.getAbsolutePath();
|
||||
if (diskPath.contains(storagePath)) {
|
||||
diskPath = diskPath.replace(storagePath + File.separator, "");
|
||||
mDisksMenu.pushPathStack(storagePath);
|
||||
}
|
||||
}
|
||||
StringTokenizer tokenizer = new StringTokenizer(diskPath, File.separator);
|
||||
while (tokenizer.hasMoreTokens()) {
|
||||
String token = tokenizer.nextToken();
|
||||
if (token.equals("")) {
|
||||
continue;
|
||||
}
|
||||
if (Apple2DisksMenu.hasDiskExtension(token)) {
|
||||
continue;
|
||||
}
|
||||
mDisksMenu.pushPathStack(token);
|
||||
}
|
||||
|
||||
Toast.makeText(Apple2Activity.this, Apple2Activity.this.getString(R.string.disk_insert_toast), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Apple2SplashScreen getSplashScreen() {
|
||||
return mSplashScreen;
|
||||
}
|
||||
|
||||
private void showSplashScreen(boolean dismissable) {
|
||||
if (mSplashScreen != null) {
|
||||
return;
|
||||
}
|
||||
mSplashScreen = new Apple2SplashScreen(this, dismissable);
|
||||
mSplashScreen.show();
|
||||
}
|
||||
|
||||
private void setupGLView() {
|
||||
|
||||
boolean glViewFirstTime = false;
|
||||
if (mView == null) {
|
||||
glViewFirstTime = true;
|
||||
mView = new Apple2View(this, mGraphicsInitializedRunnable);
|
||||
mGraphicsInitializedRunnable = null;
|
||||
mMainMenu = new Apple2MainMenu(this, mView);
|
||||
}
|
||||
|
||||
if (glViewFirstTime) {
|
||||
// HACK NOTE : do not blanket setContentView() ... it appears to wedge Gingerbread
|
||||
setContentView(mView);
|
||||
} else {
|
||||
mView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
public void registerAndShowDialog(AlertDialog dialog) {
|
||||
dialog.show();
|
||||
mAlertDialogs.add(dialog);
|
||||
}
|
||||
|
||||
public synchronized void pushApple2View(Apple2MenuView apple2MenuView) {
|
||||
mMenuStack.add(apple2MenuView);
|
||||
View menuView = apple2MenuView.getView();
|
||||
nativeEmulationPause();
|
||||
addContentView(menuView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
public synchronized Apple2MenuView popApple2View() {
|
||||
int lastIndex = mMenuStack.size() - 1;
|
||||
if (lastIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Apple2MenuView apple2MenuView = mMenuStack.remove(lastIndex);
|
||||
_disposeApple2View(apple2MenuView);
|
||||
return apple2MenuView;
|
||||
}
|
||||
|
||||
public synchronized Apple2MenuView peekApple2View() {
|
||||
int lastIndex = mMenuStack.size() - 1;
|
||||
if (lastIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mMenuStack.get(lastIndex);
|
||||
}
|
||||
|
||||
public synchronized Apple2MenuView peekApple2View(int index) {
|
||||
int lastIndex = mMenuStack.size() - 1;
|
||||
if (lastIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return mMenuStack.get(index);
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void dismissAllMenus() {
|
||||
if (mMainMenu != null) {
|
||||
mMainMenu.dismiss();
|
||||
}
|
||||
|
||||
for (AlertDialog dialog : mAlertDialogs) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
mAlertDialogs.clear();
|
||||
|
||||
// Get rid of the menu hierarchy
|
||||
ArrayList<Apple2MenuView> menuHierarchy = new ArrayList<Apple2MenuView>(mMenuStack);
|
||||
Collections.reverse(menuHierarchy);
|
||||
for (Apple2MenuView view : menuHierarchy) {
|
||||
view.dismissAll();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Apple2MenuView popApple2View(Apple2MenuView apple2MenuView) {
|
||||
boolean wasRemoved = mMenuStack.remove(apple2MenuView);
|
||||
_disposeApple2View(apple2MenuView);
|
||||
return wasRemoved ? apple2MenuView : null;
|
||||
}
|
||||
|
||||
private void _disposeApple2View(Apple2MenuView apple2MenuView) {
|
||||
|
||||
boolean dismissedSplashScreen = false;
|
||||
|
||||
// Actually remove View from view hierarchy
|
||||
{
|
||||
View menuView = apple2MenuView.getView();
|
||||
ViewGroup viewGroup = (ViewGroup) menuView.getParent();
|
||||
if (viewGroup != null) {
|
||||
viewGroup.removeView(menuView);
|
||||
}
|
||||
if (apple2MenuView instanceof Apple2SplashScreen) { // 20151101 HACK NOTE : use instanceof to avoid edge case where joystick calibration occurred (and thus the splash was already dismissed without proper mView initialization)
|
||||
mSplashScreen = null;
|
||||
dismissedSplashScreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if no more views on menu stack, resume emulation
|
||||
if (mMenuStack.size() == 0) {
|
||||
dismissAllMenus(); // NOTE : at this point, this should not be re-entrant into mMenuStack, it should just dismiss lingering popups
|
||||
if (!mPausing.get()) {
|
||||
if (dismissedSplashScreen) {
|
||||
setupGLView();
|
||||
} else {
|
||||
nativeEmulationResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeResumeCPU() {
|
||||
if (mMenuStack.size() == 0 && !mPausing.get()) {
|
||||
nativeEmulationResume();
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeQuitApp() {
|
||||
nativeEmulationPause();
|
||||
AlertDialog quitDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.quit_really).setMessage(R.string.quit_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
nativeOnQuit();
|
||||
Apple2Activity.this.finish();
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException ex) {
|
||||
// ...
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
}.run();
|
||||
}
|
||||
}).setNegativeButton(R.string.no, null).create();
|
||||
registerAndShowDialog(quitDialog);
|
||||
}
|
||||
|
||||
public void maybeReboot() {
|
||||
nativeEmulationPause();
|
||||
AlertDialog rebootDialog = new AlertDialog.Builder(this).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.reboot_really).setMessage(R.string.reboot_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
nativeReboot();
|
||||
Apple2Activity.this.mMainMenu.dismiss();
|
||||
}
|
||||
}).setNegativeButton(R.string.no, null).create();
|
||||
registerAndShowDialog(rebootDialog);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2AudioSettingsMenu extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "Apple2AudioSettingsMenu";
|
||||
|
||||
public Apple2AudioSettingsMenu(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return position == SETTINGS.MOCKINGBOARD_ENABLED.ordinal();
|
||||
}
|
||||
|
||||
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
SPEAKER_ENABLED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.speaker_enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.speaker_enable_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, true);
|
||||
cb.setEnabled(false);
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
SPEAKER_VOLUME {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.speaker_volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.speaker_volume_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.SPEAKER_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.SPEAKER_VOLUME.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
MOCKINGBOARD_ENABLED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.mockingboard_enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.mockingboard_enable_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.MOCKINGBOARD_ENABLED.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.MOCKINGBOARD_ENABLED.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
MOCKINGBOARD_VOLUME {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.mockingboard_volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.mockingboard_volume_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.Volume.MAX.ordinal() - 1, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.MOCKINGBOARD_VOLUME.saveVolume(activity, Apple2Preferences.Volume.values()[progress]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.MOCKINGBOARD_VOLUME.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + progress);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
ADVANCED_SEPARATOR {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced_summary);
|
||||
}
|
||||
},
|
||||
AUDIO_LATENCY {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.audio_latency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.audio_latency_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
if (progress == 0) {
|
||||
// disallow 0-length buffer ...
|
||||
progress = 1;
|
||||
}
|
||||
Apple2Preferences.AUDIO_LATENCY.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.AUDIO_LATENCY.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + ((float) progress / Apple2Preferences.AUDIO_LATENCY_NUM_CHOICES));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,569 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.BuildConfig;
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class Apple2CrashHandler {
|
||||
|
||||
public final static String javaCrashFileName = "jcrash.txt";
|
||||
|
||||
public static Apple2CrashHandler getInstance() {
|
||||
return sCrashHandler;
|
||||
}
|
||||
|
||||
public enum CrashType {
|
||||
JAVA_CRASH {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.crash_java_npe);
|
||||
}
|
||||
},
|
||||
NULL_DEREF {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.crash_null);
|
||||
}
|
||||
},
|
||||
STACKCALL_OVERFLOW {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.crash_stackcall_overflow);
|
||||
}
|
||||
},
|
||||
STACKBUF_OVERFLOW {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.crash_stackbuf_overflow);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static final int size = CrashType.values().length;
|
||||
|
||||
public abstract String getTitle(Apple2Activity activity);
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (CrashType setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void initializeAndSetCustomExceptionHandler(Apple2Activity activity) {
|
||||
synchronized (this) {
|
||||
if (homeDir == null) {
|
||||
homeDir = Apple2DisksMenu.getDataDir(activity);
|
||||
}
|
||||
}
|
||||
if (mDefaultExceptionHandler != null) {
|
||||
return;
|
||||
}
|
||||
mDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
||||
final Thread.UncaughtExceptionHandler defaultExceptionHandler = mDefaultExceptionHandler;
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable t) {
|
||||
try {
|
||||
Apple2CrashHandler.onUncaughtException(thread, t);
|
||||
} catch (Throwable terminator2) {
|
||||
// Yo dawg, I hear you like exceptions in your exception handler! ...
|
||||
}
|
||||
defaultExceptionHandler.uncaughtException(thread, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void abandonAllHope(Apple2Activity activity, Throwable nativeBarfed) {
|
||||
// write out the early link crash and send this through the main crash processing code
|
||||
onUncaughtException(Thread.currentThread(), nativeBarfed);
|
||||
checkForCrashes(activity);
|
||||
}
|
||||
|
||||
public boolean areJavaCrashesPresent(Apple2Activity activity) {
|
||||
File javaCrash = _javaCrashFile(activity);
|
||||
return javaCrash.exists();
|
||||
}
|
||||
|
||||
public boolean areNativeCrashesPresent(Apple2Activity activity) {
|
||||
File[] nativeCrashes = _nativeCrashFiles(activity);
|
||||
return nativeCrashes != null && nativeCrashes.length > 0;
|
||||
}
|
||||
|
||||
public boolean areCrashesPresent(Apple2Activity activity) {
|
||||
return areJavaCrashesPresent(activity) || areNativeCrashesPresent(activity);
|
||||
}
|
||||
|
||||
public void checkForCrashes(final Apple2Activity activity) {
|
||||
if (!areCrashesPresent(activity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Apple2Preferences.CRASH_CHECK.booleanValue(activity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean previouslyRanCrashCheck = mAlreadyRanCrashCheck.getAndSet(true);
|
||||
|
||||
boolean previouslySentReport = mAlreadySentReport.get();
|
||||
if (previouslySentReport) {
|
||||
|
||||
// here we assume that the crash data was previously sent via email ... if not then we lost it =P
|
||||
|
||||
Log.d(TAG, "Cleaning up crash data ...");
|
||||
int idx = 0;
|
||||
File[] nativeCrashes = _nativeCrashFiles(activity);
|
||||
for (File crash : nativeCrashes) {
|
||||
|
||||
if (!crash.delete()) {
|
||||
Log.d(TAG, "Could not unlink crash : " + crash);
|
||||
}
|
||||
|
||||
File processed = new File(_dumpPath2ProcessedPath(crash.getAbsolutePath()));
|
||||
if (!processed.delete()) {
|
||||
Log.d(TAG, "Could not unlink processed : " + processed);
|
||||
}
|
||||
}
|
||||
|
||||
File javaCrashFile = _javaCrashFile(activity);
|
||||
if (!javaCrashFile.delete()) {
|
||||
Log.d(TAG, "Could not unlink java crash : " + javaCrashFile);
|
||||
}
|
||||
|
||||
// remove previous log file
|
||||
_writeTempLogFile(activity, new StringBuilder());
|
||||
return;
|
||||
}
|
||||
|
||||
if (previouslyRanCrashCheck) {
|
||||
// don't keep asking on return from backgrounding
|
||||
return;
|
||||
}
|
||||
|
||||
final AlertDialog crashDialog = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.crasher_send).setMessage(R.string.crasher_send_message).setNegativeButton(R.string.no, null).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
|
||||
final Apple2SplashScreen splashScreen = activity.getSplashScreen();
|
||||
if (splashScreen != null) {
|
||||
splashScreen.setDismissable(false);
|
||||
}
|
||||
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
|
||||
try {
|
||||
bar.setVisibility(View.VISIBLE);
|
||||
} catch (NullPointerException npe) {
|
||||
/* could happen on early lifecycle crashes */
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
|
||||
|
||||
final int sampleRate = DevicePropertyCalculator.getRecommendedSampleRate(activity);
|
||||
final int monoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/false);
|
||||
final int stereoBufferSize = DevicePropertyCalculator.getRecommendedBufferSize(activity, /*isStereo:*/true);
|
||||
|
||||
StringBuilder summary = new StringBuilder();
|
||||
StringBuilder allCrashData = new StringBuilder();
|
||||
|
||||
// prepend information about this device
|
||||
summary.append("BRAND: ").append(Build.BRAND).append("\n");
|
||||
summary.append("MODEL: ").append(Build.MODEL).append("\n");
|
||||
summary.append("MANUFACTURER: ").append(Build.MANUFACTURER).append("\n");
|
||||
summary.append("DEVICE: ").append(Build.DEVICE).append("\n");
|
||||
summary.append("SAMPLE RATE: ").append(sampleRate).append("\n");
|
||||
summary.append("MONO BUFSIZE: ").append(monoBufferSize).append("\n");
|
||||
summary.append("STEREO BUFSIZE: ").append(stereoBufferSize).append("\n");
|
||||
summary.append("GPU VENDOR: ").append(Apple2Preferences.GL_VENDOR.stringValue(activity)).append("\n");
|
||||
summary.append("GPU RENDERER: ").append(Apple2Preferences.GL_RENDERER.stringValue(activity)).append("\n");
|
||||
summary.append("GPU VERSION: ").append(Apple2Preferences.GL_VERSION.stringValue(activity)).append("\n");
|
||||
|
||||
allCrashData.append(summary);
|
||||
|
||||
File[] nativeCrashes = _nativeCrashFiles(activity);
|
||||
if (nativeCrashes == null) {
|
||||
nativeCrashes = new File[0];
|
||||
}
|
||||
|
||||
final int len = nativeCrashes.length + 1/* maybe Java crash */ + 1/* exposeSymbols */;
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (bar != null) {
|
||||
bar.setMax(len);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (len > 0) {
|
||||
Apple2DisksMenu.exposeSymbols(activity);
|
||||
}
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (bar != null) {
|
||||
bar.setProgress(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
boolean summarizedHeader = false;
|
||||
|
||||
// iteratively process native crashes
|
||||
for (File crash : nativeCrashes) {
|
||||
|
||||
String crashPath = crash.getAbsolutePath();
|
||||
Log.d(TAG, "Processing crash : " + crashPath);
|
||||
|
||||
String processedPath = _dumpPath2ProcessedPath(crashPath);
|
||||
try {
|
||||
nativeProcessCrash(crashPath, processedPath); // Run Breakpad minidump_stackwalk
|
||||
} catch (UnsatisfiedLinkError ule) {
|
||||
/* could happen on early lifecycle crashes */
|
||||
}
|
||||
|
||||
StringBuilder crashData = new StringBuilder();
|
||||
if (!_readFile(new File(processedPath), crashData)) {
|
||||
Log.e(TAG, "Error processing crash : " + crashPath);
|
||||
}
|
||||
allCrashData.append(">>>>>>> NATIVE CRASH [").append(crashPath).append("]\n");
|
||||
allCrashData.append(crashData);
|
||||
summary.append("NATIVE CRASH:\n");
|
||||
|
||||
// append succinct information about crashing thread
|
||||
String[] lines = crashData.toString().split("[\\n\\r][\\n\\r]*");
|
||||
for (int i = 0, j = 0; i < lines.length; i++) {
|
||||
|
||||
// 2 lines of minidump summary
|
||||
if (i < 2) {
|
||||
if (!summarizedHeader) {
|
||||
summary.append(lines[i]);
|
||||
summary.append("\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1 line of crashing thread and reason
|
||||
if (i == 2) {
|
||||
summarizedHeader = true;
|
||||
summary.append(lines[i]);
|
||||
summary.append("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// whole lotta modules
|
||||
if (lines[i].startsWith("Module")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// one apparently empty line
|
||||
if (lines[i].matches("^[ \\t]*$")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// append crashing thread backtrace
|
||||
|
||||
summary.append(lines[i]);
|
||||
summary.append("\n");
|
||||
final int maxSummaryBacktrace = 8;
|
||||
if (j++ >= maxSummaryBacktrace) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (bar != null) {
|
||||
bar.setProgress(bar.getProgress() + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
StringBuilder javaCrashData = new StringBuilder();
|
||||
File javaCrashFile = _javaCrashFile(activity);
|
||||
if (javaCrashFile.exists()) {
|
||||
Log.d(TAG, "Reading java crashes file");
|
||||
if (!_readFile(javaCrashFile, javaCrashData)) {
|
||||
Log.e(TAG, "Error processing java crash : " + javaCrashFileName);
|
||||
}
|
||||
}
|
||||
|
||||
allCrashData.append(">>>>>>> JAVA CRASH DATA\n");
|
||||
allCrashData.append(javaCrashData);
|
||||
|
||||
summary.append("JAVA CRASH:\n");
|
||||
summary.append(javaCrashData);
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (bar != null) {
|
||||
bar.setProgress(bar.getProgress() + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Apple2DisksMenu.unexposeSymbols(activity);
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
bar.setVisibility(View.INVISIBLE);
|
||||
splashScreen.setDismissable(true);
|
||||
} catch (NullPointerException npe) {
|
||||
/* could happen on early lifecycle crashes */
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// send report with all the data
|
||||
_sendEmailToDeveloperWithCrashData(activity, summary, allCrashData);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}).create();
|
||||
activity.registerAndShowDialog(crashDialog);
|
||||
}
|
||||
|
||||
public void performCrash(int crashType) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
nativePerformCrash(crashType);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// privates
|
||||
|
||||
private Apple2CrashHandler() {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
private static void onUncaughtException(Thread thread, Throwable t) {
|
||||
StackTraceElement[] stackTraceElements = t.getStackTrace();
|
||||
StringBuffer traceBuffer = new StringBuffer();
|
||||
|
||||
// append the Java stack trace
|
||||
traceBuffer.append("NAME: ").append(t.getClass().getName()).append("\n");
|
||||
traceBuffer.append("MESSAGE: ").append(t.getMessage()).append("\n");
|
||||
final int maxTraceSize = 2048 + 1024 + 512; // probably should keep this less than a standard Linux PAGE_SIZE
|
||||
for (StackTraceElement elt : stackTraceElements) {
|
||||
traceBuffer.append(elt.toString());
|
||||
traceBuffer.append("\n");
|
||||
if (traceBuffer.length() >= maxTraceSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
traceBuffer.append("\n");
|
||||
|
||||
final int maxAttempts = 5;
|
||||
int attempts = 0;
|
||||
do {
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(new File(sCrashHandler.homeDir, javaCrashFileName), /*append:*/true));
|
||||
writer.append(traceBuffer);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
break;
|
||||
} catch (InterruptedIOException ie) {
|
||||
/* EINTR, EAGAIN ... */
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Exception attempting to write data : " + e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100, 0);
|
||||
} catch (InterruptedException e) {
|
||||
/* ... */
|
||||
}
|
||||
++attempts;
|
||||
} while (attempts < maxAttempts);
|
||||
}
|
||||
|
||||
private File _javaCrashFile(Apple2Activity activity) {
|
||||
return new File(homeDir, javaCrashFileName);
|
||||
}
|
||||
|
||||
private File[] _nativeCrashFiles(Apple2Activity activity) {
|
||||
FilenameFilter dmpFilter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
File file = new File(dir, name);
|
||||
if (file.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check file extensions ... sigh ... no String.endsWithIgnoreCase() ?
|
||||
|
||||
final String extension = ".dmp";
|
||||
final int nameLen = name.length();
|
||||
final int extLen = extension.length();
|
||||
if (nameLen <= extLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String suffix = name.substring(nameLen - extLen, nameLen);
|
||||
return (suffix.equalsIgnoreCase(extension));
|
||||
}
|
||||
};
|
||||
|
||||
return new File(homeDir).listFiles(dmpFilter);
|
||||
}
|
||||
|
||||
private String _dumpPath2ProcessedPath(String crashPath) {
|
||||
return crashPath.substring(0, crashPath.length() - 4) + ".txt";
|
||||
}
|
||||
|
||||
private boolean _readFile(File file, StringBuilder fileData) {
|
||||
final int maxAttempts = 5;
|
||||
int attempts = 0;
|
||||
do {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(file));
|
||||
char[] buf = new char[1024];
|
||||
int numRead = 0;
|
||||
while ((numRead = reader.read(buf)) != -1) {
|
||||
String readData = String.valueOf(buf, 0, numRead);
|
||||
fileData.append(readData);
|
||||
}
|
||||
reader.close();
|
||||
break;
|
||||
} catch (InterruptedIOException ie) {
|
||||
/* EINTR, EAGAIN ... */
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Error reading file at path : " + file.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100, 0);
|
||||
} catch (InterruptedException e) {
|
||||
/* ... */
|
||||
}
|
||||
++attempts;
|
||||
} while (attempts < maxAttempts);
|
||||
|
||||
return attempts < maxAttempts;
|
||||
}
|
||||
|
||||
private File _writeTempLogFile(Apple2Activity activity, StringBuilder allCrashData) {
|
||||
|
||||
File allCrashFile = null;
|
||||
|
||||
String storageState = Environment.getExternalStorageState();
|
||||
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
|
||||
allCrashFile = new File(Environment.getExternalStorageDirectory(), "apple2ix_crash.txt");
|
||||
} else {
|
||||
allCrashFile = new File(Apple2DisksMenu.getDataDir(activity), "apple2ix_crash.txt");
|
||||
}
|
||||
|
||||
Log.d(TAG, "Writing all crashes to temp file : " + allCrashFile.getAbsolutePath());
|
||||
final int maxAttempts = 5;
|
||||
int attempts = 0;
|
||||
do {
|
||||
try {
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(allCrashFile));
|
||||
writer.append(allCrashData);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
break;
|
||||
} catch (InterruptedIOException ie) {
|
||||
/* EINTR, EAGAIN ... */
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Exception attempting to write data : " + e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100, 0);
|
||||
} catch (InterruptedException e) {
|
||||
/* ... */
|
||||
}
|
||||
++attempts;
|
||||
} while (attempts < maxAttempts);
|
||||
|
||||
if (!allCrashFile.setReadable(true, /*ownerOnly:*/false)) {
|
||||
Log.d(TAG, "Oops, could not set all crash data readable!");
|
||||
}
|
||||
|
||||
return allCrashFile;
|
||||
}
|
||||
|
||||
private void _sendEmailToDeveloperWithCrashData(Apple2Activity activity, StringBuilder summary, StringBuilder allCrashData) {
|
||||
mAlreadySentReport.set(true);
|
||||
|
||||
// <sigh> ... the disaster that is early Android ... there does not appear to be a reliable way to start an
|
||||
// email Intent to send both text and an attachment, but we make a valiant (if futile) effort to do so here.
|
||||
// And the reason to send an attachment is that you trigger an android.os.TransactionTooLargeException with too
|
||||
// much text data in the EXTRA_TEXT ... </sigh>
|
||||
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "apple2ix_crash@deadcode.org"/*non-zero variant is correct endpoint at the moment*/, null));
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Crasher");
|
||||
|
||||
final int maxCharsEmail = 4096;
|
||||
int len = summary.length();
|
||||
len = len < maxCharsEmail ? len : maxCharsEmail;
|
||||
String summaryData = summary.substring(0, len);
|
||||
emailIntent.putExtra(Intent.EXTRA_TEXT, "The app crashed, please help!\n\n"+summaryData);
|
||||
|
||||
File allCrashFile = _writeTempLogFile(activity, allCrashData);
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(allCrashFile));
|
||||
|
||||
Log.d(TAG, "STARTING CHOOSER FOR EMAIL ...");
|
||||
activity.startActivity(Intent.createChooser(emailIntent, "Send email"));
|
||||
Log.d(TAG, "AFTER START ACTIVITY ...");
|
||||
}
|
||||
|
||||
|
||||
private final static String TAG = "Apple2CrashHandler";
|
||||
private final static Apple2CrashHandler sCrashHandler = new Apple2CrashHandler();
|
||||
|
||||
private String homeDir;
|
||||
private Thread.UncaughtExceptionHandler mDefaultExceptionHandler;
|
||||
private AtomicBoolean mAlreadyRanCrashCheck = new AtomicBoolean(false);
|
||||
private AtomicBoolean mAlreadySentReport = new AtomicBoolean(false);
|
||||
|
||||
private static native void nativePerformCrash(int crashType); // testing
|
||||
|
||||
private static native void nativeProcessCrash(String crashFilePath, String crashProcessedPath);
|
||||
|
||||
}
|
|
@ -0,0 +1,658 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RadioButton;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2DisksMenu implements Apple2MenuView {
|
||||
|
||||
private final static String TAG = "Apple2DisksMenu";
|
||||
private static String sDataDir = null;
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private View mDisksView = null;
|
||||
|
||||
private final ArrayList<String> mPathStack = new ArrayList<String>();
|
||||
|
||||
private static File sExternalFilesDir = null;
|
||||
private static File sDownloadFilesDir = null;
|
||||
private static boolean sInitializedPath = false;
|
||||
|
||||
public Apple2DisksMenu(Apple2Activity activity) {
|
||||
mActivity = activity;
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mDisksView = inflater.inflate(R.layout.activity_disks, null, false);
|
||||
|
||||
final Button cancelButton = (Button) mDisksView.findViewById(R.id.cancelButton);
|
||||
cancelButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Apple2DisksMenu.this.mActivity.dismissAllMenus();
|
||||
}
|
||||
});
|
||||
|
||||
getExternalStorageDirectory();
|
||||
}
|
||||
|
||||
public static File getExternalStorageDirectory() {
|
||||
|
||||
do {
|
||||
if (sExternalFilesDir != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
String storageState = Environment.getExternalStorageState();
|
||||
if (!storageState.equals(Environment.MEDIA_MOUNTED)) {
|
||||
// 2015/10/28 : do not expose sExternalFilesDir/sDownloadFilesDir unless they are writable
|
||||
break;
|
||||
}
|
||||
|
||||
File externalStorageDir = Environment.getExternalStorageDirectory();
|
||||
if (externalStorageDir == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
File externalDir = new File(externalStorageDir, "apple2ix"); // /sdcard/apple2ix
|
||||
if (!externalDir.exists()) {
|
||||
boolean made = externalDir.mkdirs();
|
||||
if (!made) {
|
||||
Log.d(TAG, "WARNING: could not make directory : " + sExternalFilesDir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sExternalFilesDir = externalDir;
|
||||
sDownloadFilesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
} while (false);
|
||||
|
||||
return sExternalFilesDir;
|
||||
}
|
||||
|
||||
// HACK NOTE 2015/02/22 : Apparently native code cannot easily access stuff in the APK ... so copy various resources
|
||||
// out of the APK and into the /data/data/... for ease of access. Because this is FOSS software we don't care about
|
||||
// security or DRM for these assets =)
|
||||
public static String getDataDir(Apple2Activity activity) {
|
||||
|
||||
if (sDataDir != null) {
|
||||
return sDataDir;
|
||||
}
|
||||
|
||||
try {
|
||||
PackageManager pm = activity.getPackageManager();
|
||||
PackageInfo pi = pm.getPackageInfo(activity.getPackageName(), 0);
|
||||
sDataDir = pi.applicationInfo.dataDir;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "" + e);
|
||||
if (sDataDir == null) {
|
||||
sDataDir = "/data/local/tmp";
|
||||
}
|
||||
}
|
||||
|
||||
return sDataDir;
|
||||
}
|
||||
|
||||
public static void firstTime(Apple2Activity activity) {
|
||||
final ProgressBar bar = (ProgressBar) activity.findViewById(R.id.crash_progressBar);
|
||||
try {
|
||||
bar.setVisibility(View.VISIBLE);
|
||||
bar.setIndeterminate(true);
|
||||
} catch (NullPointerException npe) {
|
||||
Log.v(TAG, "Whoa, avoided NPE in first time #1");
|
||||
}
|
||||
|
||||
getDataDir(activity);
|
||||
|
||||
Log.d(TAG, "First time copying stuff-n-things out of APK for ease-of-NDK access...");
|
||||
|
||||
getExternalStorageDirectory();
|
||||
|
||||
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"disks", /*to location:*/new File(sDataDir, "disks").getAbsolutePath());
|
||||
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/new File(sDataDir, "keyboards").getAbsolutePath());
|
||||
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"shaders", /*to location:*/new File(sDataDir, "shaders").getAbsolutePath());
|
||||
|
||||
// expose keyboards to modding
|
||||
if (sExternalFilesDir != null) {
|
||||
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"keyboards", /*to location:*/sExternalFilesDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
bar.setVisibility(View.INVISIBLE);
|
||||
bar.setIndeterminate(false);
|
||||
} catch (NullPointerException npe) {
|
||||
Log.v(TAG, "Whoa, avoided NPE in first time #2");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void exposeSymbols(Apple2Activity activity) {
|
||||
recursivelyCopyAPKAssets(activity, /*from APK directory:*/"symbols", /*to location:*/new File(sDataDir, "symbols").getAbsolutePath());
|
||||
}
|
||||
|
||||
public static void unexposeSymbols(Apple2Activity activity) {
|
||||
recursivelyDelete(new File(sDataDir, "symbols"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Apple2MenuView interface methods
|
||||
|
||||
public final boolean isCalibrating() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
if (!sInitializedPath) {
|
||||
sInitializedPath = true;
|
||||
Apple2Preferences.CURRENT_DISK_PATH.load(mActivity);
|
||||
}
|
||||
dynamicSetup();
|
||||
mActivity.pushApple2View(this);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
String path = popPathStack();
|
||||
if (path == null) {
|
||||
mActivity.popApple2View(this);
|
||||
} else {
|
||||
dynamicSetup();
|
||||
ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
|
||||
disksList.postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void dismissAll() {
|
||||
mActivity.popApple2View(this);
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mDisksView.getParent() != null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mDisksView;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// path stack methods
|
||||
|
||||
public String getPathStackJSON() {
|
||||
JSONArray jsonArray = new JSONArray(Arrays.asList(mPathStack.toArray()));
|
||||
return jsonArray.toString();
|
||||
}
|
||||
|
||||
public void setPathStackJSON(String pathStackJSON) {
|
||||
mPathStack.clear();
|
||||
try {
|
||||
JSONArray jsonArray = new JSONArray(pathStackJSON);
|
||||
for (int i = 0, count = jsonArray.length(); i < count; i++) {
|
||||
String pathComponent = jsonArray.getString(i);
|
||||
mPathStack.add(pathComponent);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void pushPathStack(String path) {
|
||||
mPathStack.add(path);
|
||||
Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON());
|
||||
}
|
||||
|
||||
public String popPathStack() {
|
||||
if (mPathStack.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
String path = mPathStack.remove(mPathStack.size() - 1);
|
||||
Apple2Preferences.CURRENT_DISK_PATH.saveString(mActivity, getPathStackJSON());
|
||||
return path;
|
||||
}
|
||||
|
||||
public static boolean hasDiskExtension(String name) {
|
||||
|
||||
// check file extensions ... sigh ... no String.endsWithIgnoreCase() ?
|
||||
|
||||
final int len = name.length();
|
||||
if (len <= 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String suffix;
|
||||
suffix = name.substring(len - 3, len);
|
||||
if (suffix.equalsIgnoreCase(".do") || suffix.equalsIgnoreCase(".po")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (len <= 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
suffix = name.substring(len - 4, len);
|
||||
if (suffix.equalsIgnoreCase(".dsk") || suffix.equalsIgnoreCase(".nib")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (len <= 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
suffix = name.substring(len - 6, len);
|
||||
if (suffix.equalsIgnoreCase(".do.gz") || suffix.equalsIgnoreCase(".po.gz")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (len <= 7) {
|
||||
return false;
|
||||
}
|
||||
|
||||
suffix = name.substring(len - 7, len);
|
||||
return (suffix.equalsIgnoreCase(".dsk.gz") || suffix.equalsIgnoreCase(".nib.gz"));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// internals ...
|
||||
|
||||
private String pathStackAsDirectory() {
|
||||
if (mPathStack.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder pathBuffer = new StringBuilder();
|
||||
for (String component : mPathStack) {
|
||||
pathBuffer.append(component);
|
||||
pathBuffer.append(File.separator);
|
||||
}
|
||||
return pathBuffer.toString();
|
||||
}
|
||||
|
||||
private static void recursivelyDelete(File file) {
|
||||
if (file.isDirectory()) {
|
||||
for (File f : file.listFiles()) {
|
||||
recursivelyDelete(f);
|
||||
}
|
||||
}
|
||||
if (!file.delete()) {
|
||||
Log.d(TAG, "Failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void recursivelyCopyAPKAssets(Apple2Activity activity, String srcFileOrDir, String dstFileOrDir) {
|
||||
AssetManager assetManager = activity.getAssets();
|
||||
|
||||
final int maxAttempts = 5;
|
||||
String[] files = null;
|
||||
int attempts = 0;
|
||||
do {
|
||||
try {
|
||||
files = assetManager.list(srcFileOrDir);
|
||||
break;
|
||||
} catch (InterruptedIOException e) {
|
||||
/* EINTR, EAGAIN ... */
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "OOPS exception attempting to list APK files at : " + srcFileOrDir + " : " + e);
|
||||
}
|
||||
|
||||
try {
|
||||
Thread.sleep(100, 0);
|
||||
} catch (InterruptedException ie) {
|
||||
/* ... */
|
||||
}
|
||||
++attempts;
|
||||
} while (attempts < maxAttempts);
|
||||
|
||||
if (files == null) {
|
||||
Log.d(TAG, "OOPS, could not list APK assets at : " + srcFileOrDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
// ensure destination directory exists
|
||||
File dstPath = new File(dstFileOrDir);
|
||||
if (!dstPath.mkdirs()) {
|
||||
if (!dstPath.exists()) {
|
||||
Log.d(TAG, "OOPS, could not mkdirs on " + dstPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (String filename : files) {
|
||||
// iterate on files and subdirectories
|
||||
recursivelyCopyAPKAssets(activity, srcFileOrDir + File.separator + filename, dstFileOrDir + File.separator + filename);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// presumably this is a file, not a subdirectory
|
||||
InputStream is = null;
|
||||
FileOutputStream os = null;
|
||||
attempts = 0;
|
||||
do {
|
||||
try {
|
||||
is = assetManager.open(srcFileOrDir);
|
||||
os = new FileOutputStream(dstFileOrDir);
|
||||
copyFile(is, os);
|
||||
break;
|
||||
} catch (InterruptedIOException e) {
|
||||
/* EINTR, EAGAIN */
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to copy asset file: " + srcFileOrDir, e);
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
if (os != null) {
|
||||
try {
|
||||
os.close();
|
||||
} catch (IOException e) {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100, 0);
|
||||
} catch (InterruptedException ie) {
|
||||
/* ... */
|
||||
}
|
||||
++attempts;
|
||||
} while (attempts < maxAttempts);
|
||||
}
|
||||
|
||||
private static void copyFile(InputStream is, FileOutputStream os) throws IOException {
|
||||
final int BUF_SZ = 4096;
|
||||
byte[] buf = new byte[BUF_SZ];
|
||||
while (true) {
|
||||
int len = is.read(buf, 0, BUF_SZ);
|
||||
if (len < 0) {
|
||||
break;
|
||||
}
|
||||
os.write(buf, 0, len);
|
||||
}
|
||||
os.flush();
|
||||
}
|
||||
|
||||
private void dynamicSetup() {
|
||||
|
||||
final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
|
||||
disksList.setEnabled(true);
|
||||
|
||||
String disksDir = pathStackAsDirectory();
|
||||
boolean isRootPath = false;
|
||||
if (disksDir == null) {
|
||||
isRootPath = true;
|
||||
disksDir = sDataDir + File.separator + "disks"; // default path
|
||||
}
|
||||
|
||||
File dir = new File(disksDir);
|
||||
|
||||
final File[] files = dir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
name = name.toLowerCase();
|
||||
if (name.equals(".")) {
|
||||
return false;
|
||||
}
|
||||
if (name.equals("..")) {
|
||||
return false;
|
||||
}
|
||||
File file = new File(dir, name);
|
||||
return file.isDirectory() || hasDiskExtension(name);
|
||||
}
|
||||
});
|
||||
|
||||
// This appears to happen in cases where the external files directory String is valid, but is not actually mounted
|
||||
// We could probably check for more media "states" in the setup above ... but this defensive coding probably should
|
||||
// remain here after any refactoring =)
|
||||
if (files == null) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
Arrays.sort(files);
|
||||
|
||||
getExternalStorageDirectory();
|
||||
final boolean includeExternalStoragePath = (sExternalFilesDir != null && isRootPath);
|
||||
final boolean includeDownloadsPath = (sDownloadFilesDir != null && isRootPath);
|
||||
final int offset = includeExternalStoragePath ? (includeDownloadsPath ? 2 : 1) : (includeDownloadsPath ? 1 : 0);
|
||||
final String[] fileNames = new String[files.length + offset];
|
||||
final boolean[] isDirectory = new boolean[files.length + offset];
|
||||
|
||||
int idx = 0;
|
||||
if (includeExternalStoragePath) {
|
||||
fileNames[idx] = sExternalFilesDir.getAbsolutePath();
|
||||
isDirectory[idx] = true;
|
||||
++idx;
|
||||
}
|
||||
if (includeDownloadsPath) {
|
||||
fileNames[idx] = sDownloadFilesDir.getAbsolutePath();
|
||||
isDirectory[idx] = true;
|
||||
++idx;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
isDirectory[idx] = file.isDirectory();
|
||||
fileNames[idx] = file.getName();
|
||||
if (isDirectory[idx]) {
|
||||
fileNames[idx] += File.separator;
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
|
||||
ArrayAdapter<?> adapter = new ArrayAdapter<String>(mActivity, R.layout.a2disk, R.id.a2disk_title, fileNames) {
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
|
||||
LinearLayout layout = (LinearLayout) view.findViewById(R.id.a2disk_widget_frame);
|
||||
if (layout.getChildCount() > 0) {
|
||||
// layout cells appear to be reused when scrolling into view ... make sure we start with clear hierarchy
|
||||
layout.removeAllViews();
|
||||
}
|
||||
|
||||
if (isDirectory[position]) {
|
||||
ImageView imageView = new ImageView(mActivity);
|
||||
Drawable drawable = mActivity.getResources().getDrawable(android.R.drawable.ic_menu_more);
|
||||
imageView.setImageDrawable(drawable);
|
||||
layout.addView(imageView);
|
||||
} else {
|
||||
|
||||
String imageName = files[position - offset].getAbsolutePath();
|
||||
final int len = imageName.length();
|
||||
final String suffix = imageName.substring(len - 3, len);
|
||||
if (suffix.equalsIgnoreCase(".gz")) {
|
||||
imageName = files[position - offset].getAbsolutePath().substring(0, len - 3);
|
||||
}
|
||||
|
||||
String eject = mActivity.getResources().getString(R.string.disk_eject);
|
||||
if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) {
|
||||
Button ejectButton = new Button(mActivity);
|
||||
ejectButton.setText(eject + " 1");
|
||||
ejectButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mActivity.nativeEjectDisk(/*driveA:*/true);
|
||||
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, "");
|
||||
dynamicSetup();
|
||||
}
|
||||
});
|
||||
layout.addView(ejectButton);
|
||||
} else if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) {
|
||||
Button ejectButton = new Button(mActivity);
|
||||
ejectButton.setText(eject + " 2");
|
||||
ejectButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mActivity.nativeEjectDisk(/*driveA:*/false);
|
||||
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, "");
|
||||
dynamicSetup();
|
||||
}
|
||||
});
|
||||
layout.addView(ejectButton);
|
||||
}
|
||||
|
||||
}
|
||||
return view;
|
||||
}
|
||||
};
|
||||
|
||||
final String parentDisksDir = disksDir;
|
||||
final boolean parentIsRootPath = isRootPath;
|
||||
|
||||
disksList.setAdapter(adapter);
|
||||
disksList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
|
||||
if (isDirectory[position]) {
|
||||
Log.d(TAG, "Descending to path : " + fileNames[position]);
|
||||
if (parentIsRootPath && !new File(fileNames[position]).isAbsolute()) {
|
||||
pushPathStack(parentDisksDir + File.separator + fileNames[position]);
|
||||
} else {
|
||||
pushPathStack(fileNames[position]);
|
||||
}
|
||||
dynamicSetup();
|
||||
ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
|
||||
disksList.postInvalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
String str = files[position - offset].getAbsolutePath();
|
||||
final int len = str.length();
|
||||
final String suffix = str.substring(len - 3, len);
|
||||
if (suffix.equalsIgnoreCase(".gz")) {
|
||||
str = files[position - offset].getAbsolutePath().substring(0, len - 3);
|
||||
}
|
||||
final String imageName = str;
|
||||
|
||||
if (imageName.equals(Apple2Preferences.CURRENT_DISK_A.stringValue(mActivity))) {
|
||||
mActivity.nativeEjectDisk(/*driveA:*/true);
|
||||
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, "");
|
||||
dynamicSetup();
|
||||
return;
|
||||
}
|
||||
if (imageName.equals(Apple2Preferences.CURRENT_DISK_B.stringValue(mActivity))) {
|
||||
mActivity.nativeEjectDisk(/*driveA:*/false);
|
||||
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, "");
|
||||
dynamicSetup();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
String title = mActivity.getResources().getString(R.string.header_disks);
|
||||
title = title + " " + fileNames[position];
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity).setCancelable(true).setMessage(title);
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final View diskConfirmationView = inflater.inflate(R.layout.a2disk_confirmation, null, false);
|
||||
builder.setView(diskConfirmationView);
|
||||
|
||||
final RadioButton diskA = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskA);
|
||||
diskA.setChecked(Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity));
|
||||
diskA.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.CURRENT_DRIVE_A_BUTTON.saveBoolean(mActivity, isChecked);
|
||||
}
|
||||
});
|
||||
final RadioButton diskB = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_diskB);
|
||||
diskB.setChecked(!Apple2Preferences.CURRENT_DRIVE_A_BUTTON.booleanValue(mActivity));
|
||||
|
||||
|
||||
final RadioButton readOnly = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readOnly);
|
||||
readOnly.setChecked(Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity));
|
||||
readOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.CURRENT_DISK_RO_BUTTON.saveBoolean(mActivity, isChecked);
|
||||
}
|
||||
});
|
||||
|
||||
final RadioButton readWrite = (RadioButton) diskConfirmationView.findViewById(R.id.radioButton_readWrite);
|
||||
readWrite.setChecked(!Apple2Preferences.CURRENT_DISK_RO_BUTTON.booleanValue(mActivity));
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
boolean isDriveA = diskA.isChecked();
|
||||
boolean diskReadOnly = readOnly.isChecked();
|
||||
if (isDriveA) {
|
||||
Apple2Preferences.CURRENT_DISK_A_RO.saveBoolean(mActivity, diskReadOnly);
|
||||
Apple2Preferences.CURRENT_DISK_A.saveString(mActivity, imageName);
|
||||
} else {
|
||||
Apple2Preferences.CURRENT_DISK_B_RO.saveBoolean(mActivity, diskReadOnly);
|
||||
Apple2Preferences.CURRENT_DISK_B.saveString(mActivity, imageName);
|
||||
}
|
||||
dialog.dismiss();
|
||||
mActivity.dismissAllMenus();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
mActivity.registerAndShowDialog(dialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.SeekBar;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Apple2JoystickCalibration implements Apple2MenuView {
|
||||
|
||||
private final static String TAG = "Apple2JoystickCalibration";
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private View mSettingsView = null;
|
||||
private ArrayList<Apple2MenuView> mViewStack = null;
|
||||
private boolean mTouchMenuEnabled = false;
|
||||
private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal();
|
||||
|
||||
public Apple2JoystickCalibration(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack, Apple2Preferences.TouchDeviceVariant variant) {
|
||||
mActivity = activity;
|
||||
mViewStack = viewStack;
|
||||
if (!(variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK || variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD)) {
|
||||
throw new RuntimeException("You're doing it wrong");
|
||||
}
|
||||
|
||||
setup(variant);
|
||||
}
|
||||
|
||||
private void setup(Apple2Preferences.TouchDeviceVariant variant) {
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mSettingsView = inflater.inflate(R.layout.activity_calibrate_joystick, null, false);
|
||||
|
||||
SeekBar sb = (SeekBar) mSettingsView.findViewById(R.id.seekBar);
|
||||
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
return;
|
||||
}
|
||||
Apple2Preferences.JOYSTICK_DIVIDER.saveInt(mActivity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
});
|
||||
|
||||
sb.setMax(0); // http://stackoverflow.com/questions/10278467/seekbar-not-setting-actual-progress-setprogress-not-working-on-early-android
|
||||
sb.setMax(Apple2Preferences.JOYSTICK_DIVIDER_NUM_CHOICES);
|
||||
sb.setProgress(Apple2Preferences.JOYSTICK_DIVIDER.intValue(mActivity));
|
||||
|
||||
mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity);
|
||||
Apple2Preferences.nativeSetTouchMenuEnabled(false);
|
||||
mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity);
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(variant.ordinal());
|
||||
if (variant == Apple2Preferences.TouchDeviceVariant.JOYSTICK) {
|
||||
Apple2Preferences.loadAllJoystickButtons(mActivity);
|
||||
} else {
|
||||
Apple2Preferences.loadAllKeypadKeys(mActivity);
|
||||
}
|
||||
|
||||
Apple2Preferences.nativeTouchDeviceBeginCalibrationMode();
|
||||
}
|
||||
|
||||
public final boolean isCalibrating() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
mActivity.pushApple2View(this);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
for (Apple2MenuView apple2MenuView : mViewStack) {
|
||||
if (apple2MenuView != this) {
|
||||
mActivity.pushApple2View(apple2MenuView);
|
||||
}
|
||||
}
|
||||
|
||||
Apple2Preferences.nativeTouchDeviceEndCalibrationMode();
|
||||
Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled);
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice);
|
||||
|
||||
mActivity.popApple2View(this);
|
||||
}
|
||||
|
||||
public void dismissAll() {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mSettingsView.getParent() != null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mSettingsView;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,447 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2JoystickSettingsMenu extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "Apple2JoystickSettingsMenu";
|
||||
|
||||
public Apple2JoystickSettingsMenu(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
JOYSTICK_TAP_BUTTON {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_tap_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_tap_button_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
_alertDialogHandleSelection(activity, R.string.joystick_button_tap_button, new String[]{
|
||||
activity.getResources().getString(R.string.joystick_button_button_none),
|
||||
activity.getResources().getString(R.string.joystick_button_button1),
|
||||
activity.getResources().getString(R.string.joystick_button_button2),
|
||||
activity.getResources().getString(R.string.joystick_button_button_both),
|
||||
}, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_TAP_BUTTON.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.JOYSTICK_TAP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_SWIPEUP_BUTTON {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_swipe_up_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_swipe_up_button_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_up_button, new String[]{
|
||||
activity.getResources().getString(R.string.joystick_button_button_none),
|
||||
activity.getResources().getString(R.string.joystick_button_button1),
|
||||
activity.getResources().getString(R.string.joystick_button_button2),
|
||||
activity.getResources().getString(R.string.joystick_button_button_both),
|
||||
}, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.JOYSTICK_SWIPEUP_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_SWIPEDOWN_BUTTON {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_swipe_down_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_swipe_down_button_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
_alertDialogHandleSelection(activity, R.string.joystick_button_swipe_down_button, new String[]{
|
||||
activity.getResources().getString(R.string.joystick_button_button_none),
|
||||
activity.getResources().getString(R.string.joystick_button_button1),
|
||||
activity.getResources().getString(R.string.joystick_button_button2),
|
||||
activity.getResources().getString(R.string.joystick_button_button_both),
|
||||
}, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.JOYSTICK_SWIPEDOWN_BUTTON.saveTouchJoystickButtons(activity, Apple2Preferences.TouchJoystickButtons.values()[value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_CALIBRATE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_calibrate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_calibrate_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
|
||||
{
|
||||
int idx = 0;
|
||||
while (true) {
|
||||
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
|
||||
if (apple2MenuView == null) {
|
||||
break;
|
||||
}
|
||||
viewStack.add(apple2MenuView);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK);
|
||||
|
||||
// show this new view...
|
||||
calibration.show();
|
||||
|
||||
// ...with nothing else underneath 'cept the emulator OpenGL layer
|
||||
for (Apple2MenuView apple2MenuView : viewStack) {
|
||||
activity.popApple2View(apple2MenuView);
|
||||
}
|
||||
}
|
||||
},
|
||||
JOYSTICK_TAPDELAY {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_tapdelay_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.TAPDELAY_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.JOYSTICK_TAPDELAY.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_TAPDELAY.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + (((float) progress / Apple2Preferences.TAPDELAY_NUM_CHOICES) * Apple2Preferences.TAPDELAY_SCALE));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_ADVANCED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced_joystick_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show();
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JoystickAdvanced extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "JoystickAdvanced";
|
||||
|
||||
public JoystickAdvanced(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return position == SETTINGS.JOYSTICK_AXIS_ON_LEFT.ordinal();
|
||||
}
|
||||
|
||||
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
JOYSTICK_VISIBILITY {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_visible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_visible_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_VISIBILITY.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.JOYSTICK_VISIBILITY.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
JOYSTICK_AXIS_ON_LEFT {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_axisleft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_axisleft_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.JOYSTICK_AXIS_ON_LEFT.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
JOYSTICK_AXIS_SENSITIVIY {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_axis_sensitivity_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.JOYSTICK_AXIS_SENSITIVITY_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
saveInt(progress);
|
||||
int percent = (int) (Apple2Preferences.JOYSTICK_AXIS_SENSITIVIY.floatValue(activity) * 100.f);
|
||||
seekBarValue.setText("" + percent + "%");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_BUTTON_THRESHOLD {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_button_threshold_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
if (progress == 0) {
|
||||
progress = 1;
|
||||
}
|
||||
Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
int threshold = progress * Apple2Preferences.JOYSTICK_BUTTON_THRESHOLD_STEP;
|
||||
seekBarValue.setText("" + threshold + " pts");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2KeyboardSettingsMenu extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "KeyboardSettingsMenu";
|
||||
|
||||
// These settings must match native side
|
||||
public final static int MOUSETEXT_BEGIN = 0x80;
|
||||
public final static int MOUSETEXT_CLOSEDAPPLE = MOUSETEXT_BEGIN/*+0x00*/;
|
||||
public final static int MOUSETEXT_OPENAPPLE = MOUSETEXT_BEGIN + 0x01;
|
||||
public final static int MOUSETEXT_LEFT = MOUSETEXT_BEGIN + 0x08;
|
||||
public final static int MOUSETEXT_UP = MOUSETEXT_BEGIN + 0x0b;
|
||||
public final static int MOUSETEXT_DOWN = MOUSETEXT_BEGIN + 0x0a;
|
||||
public final static int MOUSETEXT_RIGHT = MOUSETEXT_BEGIN + 0x15;
|
||||
|
||||
public final static int ICONTEXT_BEGIN = 0xA0;
|
||||
public final static int ICONTEXT_VISUAL_SPACE = ICONTEXT_BEGIN + 0x11;
|
||||
public final static int ICONTEXT_KBD_BEGIN = ICONTEXT_BEGIN + 0x13;
|
||||
public final static int ICONTEXT_CTRL = ICONTEXT_KBD_BEGIN/* + 0x00*/;
|
||||
public final static int ICONTEXT_ESC = ICONTEXT_KBD_BEGIN + 0x09;
|
||||
public final static int ICONTEXT_RETURN = ICONTEXT_KBD_BEGIN + 0x0A;
|
||||
public final static int ICONTEXT_NONACTION = ICONTEXT_KBD_BEGIN + 0x0C;
|
||||
|
||||
public final static int SCANCODE_A = 30;
|
||||
public final static int SCANCODE_D = 32;
|
||||
public final static int SCANCODE_F = 33;
|
||||
public final static int SCANCODE_H = 35;
|
||||
public final static int SCANCODE_I = 23;
|
||||
public final static int SCANCODE_J = 36;
|
||||
public final static int SCANCODE_K = 37;
|
||||
public final static int SCANCODE_L = 38;
|
||||
public final static int SCANCODE_M = 50;
|
||||
public final static int SCANCODE_N = 49;
|
||||
public final static int SCANCODE_O = 24;
|
||||
public final static int SCANCODE_U = 22;
|
||||
public final static int SCANCODE_W = 17;
|
||||
public final static int SCANCODE_X = 45;
|
||||
public final static int SCANCODE_Y = 21;
|
||||
public final static int SCANCODE_Z = 44;
|
||||
public final static int SCANCODE_SPACE = 57;
|
||||
public final static int SCANCODE_UP = 103;
|
||||
public final static int SCANCODE_LEFT = 105;
|
||||
public final static int SCANCODE_RIGHT = 106;
|
||||
public final static int SCANCODE_DOWN = 108;
|
||||
public final static int SCANCODE_COMMA = 51;
|
||||
|
||||
public Apple2KeyboardSettingsMenu(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
KEYBOARD_VISIBILITY_INACTIVE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_visibility_inactive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_visibility_inactive_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.KEYBOARD_VISIBILITY_INACTIVE.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
KEYBOARD_VISIBILITY_ACTIVE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_visibility_active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_visibility_active_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.KEYBOARD_VISIBILITY_ACTIVE.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
KEYBOARD_ENABLE_CLICK {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_click_enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_click_enabled_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_CLICK_ENABLED.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.KEYBOARD_CLICK_ENABLED.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
KEYBOARD_ENABLE_LOWERCASE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_lowercase_enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_lowercase_enabled_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.KEYBOARD_LOWERCASE_ENABLED.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
KEYBOARD_CHOOSE_ALT {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_choose_alt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_choose_alt_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
|
||||
File extKeyboardDir = Apple2DisksMenu.getExternalStorageDirectory();
|
||||
|
||||
FilenameFilter kbdJsonFilter = new FilenameFilter() {
|
||||
public boolean accept(File dir, String name) {
|
||||
File file = new File(dir, name);
|
||||
if (file.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check file extensions ... sigh ... no String.endsWithIgnoreCase() ?
|
||||
|
||||
final String extension = ".kbd.json";
|
||||
final int nameLen = name.length();
|
||||
final int extLen = extension.length();
|
||||
if (nameLen <= extLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String suffix = name.substring(nameLen - extLen, nameLen);
|
||||
return (suffix.equalsIgnoreCase(extension));
|
||||
}
|
||||
};
|
||||
|
||||
File[] files = null;
|
||||
if (extKeyboardDir != null) {
|
||||
files = extKeyboardDir.listFiles(kbdJsonFilter);
|
||||
}
|
||||
if (files == null) {
|
||||
// read keyboard data from /data/data/...
|
||||
File keyboardDir = new File(Apple2DisksMenu.getDataDir(activity) + File.separator + "keyboards");
|
||||
files = keyboardDir.listFiles(kbdJsonFilter);
|
||||
if (files == null) {
|
||||
Log.e(TAG, "OOPS, could not read keyboard data directory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Arrays.sort(files);
|
||||
|
||||
final File[] allFiles = files;
|
||||
String[] titles = new String[allFiles.length];
|
||||
int idx = 0;
|
||||
for (File file : allFiles) {
|
||||
titles[idx] = file.getName();
|
||||
++idx;
|
||||
}
|
||||
|
||||
final String keyboardDirName = extKeyboardDir == null ? "Keyboards" : extKeyboardDir.getPath();
|
||||
|
||||
_alertDialogHandleSelection(activity, keyboardDirName, titles, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.KEYBOARD_ALT.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.KEYBOARD_ALT.saveInt(activity, value);
|
||||
String path = allFiles[value].getPath();
|
||||
Apple2Preferences.KEYBOARD_ALT_PATH.saveString(activity, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2KeypadChooser implements Apple2MenuView {
|
||||
|
||||
private final static String TAG = "Apple2KeypadChooser";
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private View mSettingsView = null;
|
||||
private ArrayList<Apple2MenuView> mViewStack = null;
|
||||
private TextView mCurrentChoicePrompt = null;
|
||||
|
||||
private String[] foo = null;
|
||||
|
||||
private STATE_MACHINE mChooserState = STATE_MACHINE.CHOOSE_NORTHWEST;
|
||||
|
||||
private boolean mTouchMenuEnabled = false;
|
||||
private int mSavedTouchDevice = Apple2Preferences.TouchDeviceVariant.NONE.ordinal();
|
||||
|
||||
public Apple2KeypadChooser(Apple2Activity activity, ArrayList<Apple2MenuView> viewStack) {
|
||||
mActivity = activity;
|
||||
mViewStack = viewStack;
|
||||
setup();
|
||||
}
|
||||
|
||||
public final boolean isCalibrating() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
|
||||
if (ascii == Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION) {
|
||||
scancode = -1;
|
||||
}
|
||||
if (scancode == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String asciiStr = asciiRepresentation(ascii);
|
||||
Log.d(TAG, "ascii:'" + asciiStr + "' scancode:" + scancode);
|
||||
mChooserState.setValues(mActivity, ascii, scancode);
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD.ordinal());
|
||||
mCurrentChoicePrompt.setText(getNextChoiceString() + asciiStr);
|
||||
switch (mChooserState) {
|
||||
case CHOOSE_TAP:
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{400.f});
|
||||
break;
|
||||
case CHOOSE_SWIPEDOWN:
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{600.f});
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{600.f});
|
||||
break;
|
||||
case CHOOSE_SWIPEUP:
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_DOWN, 1, 0, new float[]{400.f}, new float[]{400.f});
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_MOVE, 1, 0, new float[]{400.f}, new float[]{200.f});
|
||||
mActivity.nativeOnTouch(MotionEvent.ACTION_UP, 1, 0, new float[]{400.f}, new float[]{200.f});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mChooserState = mChooserState.next();
|
||||
mCurrentChoicePrompt.setText(getNextChoiceString());
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal());
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
mActivity.pushApple2View(this);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
for (Apple2MenuView apple2MenuView : mViewStack) {
|
||||
if (apple2MenuView != this) {
|
||||
mActivity.pushApple2View(apple2MenuView);
|
||||
}
|
||||
}
|
||||
|
||||
Apple2Preferences.nativeTouchDeviceEndCalibrationMode();
|
||||
Apple2Preferences.nativeSetTouchMenuEnabled(mTouchMenuEnabled);
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(mSavedTouchDevice);
|
||||
|
||||
mActivity.popApple2View(this);
|
||||
}
|
||||
|
||||
public void dismissAll() {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mSettingsView.getParent() != null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mSettingsView;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// internals
|
||||
|
||||
private void setup() {
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mSettingsView = inflater.inflate(R.layout.activity_chooser_keypad, null, false);
|
||||
|
||||
mCurrentChoicePrompt = (TextView) mSettingsView.findViewById(R.id.currentChoicePrompt);
|
||||
mCurrentChoicePrompt.setText(getNextChoiceString());
|
||||
|
||||
Button skipButton = (Button) mSettingsView.findViewById(R.id.skipButton);
|
||||
skipButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Apple2KeypadChooser.this.onKeyTapCalibrationEvent((char)Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION, -1);
|
||||
}
|
||||
});
|
||||
|
||||
// temporarily undo these native touch settings while calibrating...
|
||||
mTouchMenuEnabled = Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(mActivity);
|
||||
Apple2Preferences.nativeSetTouchMenuEnabled(false);
|
||||
mSavedTouchDevice = Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(mActivity);
|
||||
Apple2Preferences.nativeSetCurrentTouchDevice(Apple2Preferences.TouchDeviceVariant.KEYBOARD.ordinal());
|
||||
|
||||
Apple2Preferences.nativeTouchDeviceBeginCalibrationMode();
|
||||
}
|
||||
|
||||
private String asciiRepresentation(char ascii) {
|
||||
switch (ascii) {
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_OPENAPPLE:
|
||||
return mActivity.getResources().getString(R.string.key_open_apple);
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_CLOSEDAPPLE:
|
||||
return mActivity.getResources().getString(R.string.key_closed_apple);
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_UP:
|
||||
return mActivity.getResources().getString(R.string.key_up);
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_LEFT:
|
||||
return mActivity.getResources().getString(R.string.key_left);
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_RIGHT:
|
||||
return mActivity.getResources().getString(R.string.key_right);
|
||||
case Apple2KeyboardSettingsMenu.MOUSETEXT_DOWN:
|
||||
return mActivity.getResources().getString(R.string.key_down);
|
||||
case Apple2KeyboardSettingsMenu.ICONTEXT_CTRL:
|
||||
return mActivity.getResources().getString(R.string.key_ctrl);
|
||||
case Apple2KeyboardSettingsMenu.ICONTEXT_ESC:
|
||||
return mActivity.getResources().getString(R.string.key_esc);
|
||||
case Apple2KeyboardSettingsMenu.ICONTEXT_RETURN:
|
||||
return mActivity.getResources().getString(R.string.key_ret);
|
||||
case Apple2KeyboardSettingsMenu.ICONTEXT_NONACTION:
|
||||
return mActivity.getResources().getString(R.string.key_none);
|
||||
case ' ':
|
||||
case Apple2KeyboardSettingsMenu.ICONTEXT_VISUAL_SPACE:
|
||||
return mActivity.getResources().getString(R.string.key_space);
|
||||
default:
|
||||
return "" + ascii;
|
||||
}
|
||||
}
|
||||
|
||||
private String getNextChoiceString() {
|
||||
String choose = mActivity.getResources().getString(R.string.keypad_choose_current);
|
||||
return choose.replace("XXX", mChooserState.getKeyName(mActivity));
|
||||
}
|
||||
|
||||
private enum STATE_MACHINE {
|
||||
CHOOSE_NORTHWEST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_ul);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_NORTHWEST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_NORTH {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_up);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_NORTH_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_NORTHEAST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_ur);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_NORTHEAST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_WEST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_WEST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_CENTER {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_CENTER_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_EAST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_EAST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_SOUTHWEST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_dl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_SOUTHWEST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_SOUTH {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_dn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_SOUTH_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_SOUTHEAST {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_axis_dr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_SOUTHEAST_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_TAP {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_button_tap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_TAP_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_SWIPEUP {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_button_swipeup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_SWIPEUP_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
},
|
||||
CHOOSE_SWIPEDOWN {
|
||||
@Override
|
||||
public String getKeyName(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_key_button_swipedown);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValues(Apple2Activity activity, char ascii, int scancode) {
|
||||
Apple2Preferences.KEYPAD_SWIPEDOWN_KEY.saveChosenKey(activity, ascii, scancode);
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = STATE_MACHINE.values().length;
|
||||
|
||||
public abstract void setValues(Apple2Activity activity, char ascii, int scancode);
|
||||
|
||||
public abstract String getKeyName(Apple2Activity activity);
|
||||
|
||||
public STATE_MACHINE next() {
|
||||
int nextOrd = this.ordinal() + 1;
|
||||
if (nextOrd >= size) {
|
||||
nextOrd = 0;
|
||||
}
|
||||
STATE_MACHINE nextState = STATE_MACHINE.values()[nextOrd];
|
||||
return nextState;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2KeypadSettingsMenu extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "Apple2KeypadSettingsMenu";
|
||||
|
||||
public Apple2KeypadSettingsMenu(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
KEYPAD_CHOOSE_KEYS {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_choose);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_choose_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
String[] titles = new String[Apple2Preferences.KeypadPreset.size + 1];
|
||||
titles[0] = activity.getResources().getString(R.string.keypad_preset_custom);
|
||||
System.arraycopy(Apple2Preferences.KeypadPreset.titles(activity), 0, titles, 1, Apple2Preferences.KeypadPreset.size);
|
||||
|
||||
_alertDialogHandleSelection(activity, R.string.keypad_choose_title, titles, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.KEYPAD_KEYS.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.KEYPAD_KEYS.saveInt(activity, value);
|
||||
if (value == 0) {
|
||||
Apple2KeypadSettingsMenu keypadSettingsMenu = (Apple2KeypadSettingsMenu) settingsMenu;
|
||||
keypadSettingsMenu.chooseKeys(activity);
|
||||
} else {
|
||||
Apple2Preferences.KeypadPreset.values()[value - 1].apply(activity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
KEYPAD_CALIBRATE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_calibrate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_calibrate_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
|
||||
{
|
||||
int idx = 0;
|
||||
while (true) {
|
||||
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
|
||||
if (apple2MenuView == null) {
|
||||
break;
|
||||
}
|
||||
viewStack.add(apple2MenuView);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
Apple2JoystickCalibration calibration = new Apple2JoystickCalibration(activity, viewStack, Apple2Preferences.TouchDeviceVariant.JOYSTICK_KEYPAD);
|
||||
|
||||
// show this new view...
|
||||
calibration.show();
|
||||
|
||||
// ...with nothing else underneath 'cept the emulator OpenGL layer
|
||||
for (Apple2MenuView apple2MenuView : viewStack) {
|
||||
activity.popApple2View(apple2MenuView);
|
||||
}
|
||||
}
|
||||
},
|
||||
KEYPAD_ADVANCED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced_joystick_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2KeypadSettingsMenu.KeypadAdvanced(activity).show();
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// internals
|
||||
|
||||
private void chooseKeys(Apple2Activity activity) {
|
||||
ArrayList<Apple2MenuView> viewStack = new ArrayList<Apple2MenuView>();
|
||||
{
|
||||
int idx = 0;
|
||||
while (true) {
|
||||
Apple2MenuView apple2MenuView = activity.peekApple2View(idx);
|
||||
if (apple2MenuView == null) {
|
||||
break;
|
||||
}
|
||||
viewStack.add(apple2MenuView);
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
Apple2KeypadChooser chooser = new Apple2KeypadChooser(activity, viewStack);
|
||||
|
||||
// show this new view...
|
||||
chooser.show();
|
||||
|
||||
// ...with nothing else underneath 'cept the emulator OpenGL layer
|
||||
for (Apple2MenuView apple2MenuView : viewStack) {
|
||||
activity.popApple2View(apple2MenuView);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class KeypadAdvanced extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "KeypadAdvanced";
|
||||
|
||||
public KeypadAdvanced(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return position == SETTINGS.JOYSTICK_ADVANCED.ordinal();
|
||||
}
|
||||
|
||||
protected enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
KEYREPEAT_THRESHOLD {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_repeat_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.KEYREPEAT_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.KEYREPEAT_THRESHOLD.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.KEYREPEAT_THRESHOLD.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + ((float) progress / Apple2Preferences.KEYREPEAT_NUM_CHOICES));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_ADVANCED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced_joystick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.settings_advanced_joystick_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2JoystickSettingsMenu.JoystickAdvanced(activity).show();
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2MainMenu {
|
||||
|
||||
private final static int MENU_INSET = 20;
|
||||
private final static String TAG = "Apple2MainMenu";
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private Apple2View mParentView = null;
|
||||
private PopupWindow mMainMenuPopup = null;
|
||||
|
||||
public Apple2MainMenu(Apple2Activity activity, Apple2View parent) {
|
||||
mActivity = activity;
|
||||
mParentView = parent;
|
||||
setup();
|
||||
}
|
||||
|
||||
enum SETTINGS {
|
||||
SHOW_SETTINGS {
|
||||
@Override public String getTitle(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.menu_settings);
|
||||
}
|
||||
@Override public String getSummary(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.menu_settings_summary);
|
||||
}
|
||||
@Override public void handleSelection(Apple2MainMenu mainMenu) {
|
||||
mainMenu.showSettings();
|
||||
}
|
||||
},
|
||||
LOAD_DISK {
|
||||
@Override public String getTitle(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.menu_disks);
|
||||
}
|
||||
@Override public String getSummary(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.menu_disks_summary);
|
||||
}
|
||||
@Override public void handleSelection(Apple2MainMenu mainMenu) {
|
||||
mainMenu.showDisksMenu();
|
||||
}
|
||||
},
|
||||
REBOOT_EMULATOR {
|
||||
@Override public String getTitle(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.reboot);
|
||||
}
|
||||
@Override public String getSummary(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.reboot_summary);
|
||||
}
|
||||
@Override public void handleSelection(Apple2MainMenu mainMenu) {
|
||||
mainMenu.mActivity.maybeReboot();
|
||||
}
|
||||
},
|
||||
QUIT_EMULATOR {
|
||||
@Override public String getTitle(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.quit);
|
||||
}
|
||||
@Override public String getSummary(Context ctx) {
|
||||
return ctx.getResources().getString(R.string.quit_summary);
|
||||
}
|
||||
@Override public void handleSelection(Apple2MainMenu mainMenu) {
|
||||
mainMenu.mActivity.maybeQuitApp();
|
||||
}
|
||||
};
|
||||
|
||||
public abstract String getTitle(Context ctx);
|
||||
public abstract String getSummary(Context ctx);
|
||||
public abstract void handleSelection(Apple2MainMenu mainMenu);
|
||||
|
||||
public static String[] titles(Context ctx) {
|
||||
String[] titles = new String[values().length];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(ctx);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
View listLayout=inflater.inflate(R.layout.activity_main_menu, null, false);
|
||||
ListView mainMenuView = (ListView)listLayout.findViewById(R.id.main_popup_menu);
|
||||
mainMenuView.setEnabled(true);
|
||||
LinearLayout mainPopupContainer = (LinearLayout)listLayout.findViewById(R.id.main_popup_container);
|
||||
|
||||
final String[] values = SETTINGS.titles(mActivity);
|
||||
|
||||
ArrayAdapter<?> adapter = new ArrayAdapter<String>(mActivity, android.R.layout.simple_list_item_2, android.R.id.text1, values) {
|
||||
@Override
|
||||
public boolean areAllItemsEnabled() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
TextView tv = (TextView)view.findViewById(android.R.id.text2);
|
||||
SETTINGS setting = SETTINGS.values()[position];
|
||||
tv.setText(setting.getSummary(mActivity));
|
||||
return view;
|
||||
}
|
||||
};
|
||||
mainMenuView.setAdapter(adapter);
|
||||
mainMenuView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Log.d(TAG, "position:"+position+" tapped...");
|
||||
SETTINGS setting = SETTINGS.values()[position];
|
||||
setting.handleSelection(Apple2MainMenu.this);
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
mMainMenuPopup = new PopupWindow(mainPopupContainer, android.app.ActionBar.LayoutParams.WRAP_CONTENT, android.app.ActionBar.LayoutParams.WRAP_CONTENT, true);
|
||||
} else {
|
||||
// 2015/03/11 ... there may well be a less hackish way to support Gingerbread, but eh ... diminishing returns
|
||||
final int TOTAL_MARGINS = 16;
|
||||
int totalHeight = TOTAL_MARGINS;
|
||||
int maxWidth = 0;
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
View view = adapter.getView(i, null, mainMenuView);
|
||||
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
|
||||
totalHeight += view.getMeasuredHeight();
|
||||
int width = view.getMeasuredWidth();
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width;
|
||||
}
|
||||
}
|
||||
mMainMenuPopup = new PopupWindow(mainPopupContainer, maxWidth+TOTAL_MARGINS, totalHeight, true);
|
||||
}
|
||||
|
||||
// This kludgery allows touching the outside or back-buttoning to dismiss
|
||||
mMainMenuPopup.setBackgroundDrawable(new BitmapDrawable());
|
||||
mMainMenuPopup.setOutsideTouchable(true);
|
||||
mMainMenuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
Apple2MainMenu.this.mActivity.maybeResumeCPU();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showDisksMenu() {
|
||||
Apple2DisksMenu disksMenu = mActivity.getDisksMenu();
|
||||
disksMenu.show();
|
||||
mMainMenuPopup.dismiss();
|
||||
}
|
||||
|
||||
public void showSettings() {
|
||||
Apple2SettingsMenu settings = mActivity.getSettingsMenu();
|
||||
settings.show();
|
||||
mMainMenuPopup.dismiss();
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (mMainMenuPopup.isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mActivity.nativeEmulationPause();
|
||||
|
||||
mMainMenuPopup.showAtLocation(mParentView, Gravity.CENTER, 0, 0);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
if (mMainMenuPopup.isShowing()) {
|
||||
mMainMenuPopup.dismiss();
|
||||
// listener will resume ...
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mMainMenuPopup.isShowing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
public interface Apple2MenuView {
|
||||
|
||||
public void show();
|
||||
|
||||
public boolean isShowing();
|
||||
|
||||
public void dismiss();
|
||||
|
||||
public void dismissAll();
|
||||
|
||||
public View getView();
|
||||
|
||||
public boolean isCalibrating();
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode);
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.BuildConfig;
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2SettingsMenu extends Apple2AbstractMenu {
|
||||
|
||||
private final static String TAG = "Apple2SettingsMenu";
|
||||
|
||||
public Apple2SettingsMenu(Apple2Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String[] allTitles() {
|
||||
return SETTINGS.titles(mActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IMenuEnum[] allValues() {
|
||||
return SETTINGS.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean areAllItemsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isEnabled(int position) {
|
||||
if (position < 0 || position >= SETTINGS.size) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return position != SETTINGS.TOUCH_MENU_VISIBILITY.ordinal();
|
||||
}
|
||||
|
||||
enum SETTINGS implements Apple2AbstractMenu.IMenuEnum {
|
||||
CURRENT_INPUT {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.input_current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.input_current_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
_alertDialogHandleSelection(activity, R.string.input_current, new String[]{
|
||||
activity.getResources().getString(R.string.joystick),
|
||||
activity.getResources().getString(R.string.keypad),
|
||||
activity.getResources().getString(R.string.keyboard),
|
||||
}, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.CURRENT_TOUCH_DEVICE.intValue(activity) - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.CURRENT_TOUCH_DEVICE.saveTouchDevice(activity, Apple2Preferences.TouchDeviceVariant.values()[value + 1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
JOYSTICK_CONFIGURE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.joystick_configure_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2JoystickSettingsMenu(activity).show();
|
||||
}
|
||||
},
|
||||
KEYPAD_CONFIGURE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keypad_configure_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2KeypadSettingsMenu(activity).show();
|
||||
}
|
||||
},
|
||||
KEYBOARD_CONFIGURE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.keyboard_configure_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2KeyboardSettingsMenu(activity).show();
|
||||
}
|
||||
},
|
||||
AUDIO_CONFIGURE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.audio_configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.audio_configure_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
new Apple2AudioSettingsMenu(activity).show();
|
||||
}
|
||||
},
|
||||
VIDEO_CONFIGURE {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.video_configure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.video_configure_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
_addPopupIcon(activity, this, convertView);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
_alertDialogHandleSelection(activity, R.string.video_configure, new String[]{
|
||||
settingsMenu.mActivity.getResources().getString(R.string.color_bw),
|
||||
settingsMenu.mActivity.getResources().getString(R.string.color_color),
|
||||
settingsMenu.mActivity.getResources().getString(R.string.color_interpolated),
|
||||
}, new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.HIRES_COLOR.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
Apple2Preferences.HIRES_COLOR.saveHiresColor(settingsMenu.mActivity, Apple2Preferences.HiresColor.values()[value]);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
TOUCH_MENU_ENABLED {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.touch_menu_enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.touch_menu_enable_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.TOUCH_MENU_ENABLED.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.TOUCH_MENU_ENABLED.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
},
|
||||
TOUCH_MENU_VISIBILITY {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.touch_menu_visibility);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.touch_menu_visibility_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
return _sliderView(activity, this, Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES, new IPreferenceSlider() {
|
||||
@Override
|
||||
public void saveInt(int progress) {
|
||||
Apple2Preferences.TOUCH_MENU_VISIBILITY.saveInt(activity, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return Apple2Preferences.TOUCH_MENU_VISIBILITY.intValue(activity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showValue(int progress, final TextView seekBarValue) {
|
||||
seekBarValue.setText("" + ((float) progress / Apple2Preferences.ALPHA_SLIDER_NUM_CHOICES));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
ABOUT {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.about_apple2ix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.about_apple2ix_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
String url = "http://deadc0de.org/apple2ix/android/";
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(url));
|
||||
activity.startActivity(i);
|
||||
}
|
||||
},
|
||||
RESET_PREFERENCES {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.preferences_reset_title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
return activity.getResources().getString(R.string.preferences_reset_summary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity).setIcon(R.drawable.ic_launcher).setCancelable(true).setTitle(R.string.preferences_reset_really).setMessage(R.string.preferences_reset_warning).setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
Apple2Preferences.resetPreferences(activity);
|
||||
}
|
||||
}).setNegativeButton(R.string.no, null);
|
||||
AlertDialog dialog = builder.create();
|
||||
activity.registerAndShowDialog(dialog);
|
||||
}
|
||||
},
|
||||
CRASH {
|
||||
@Override
|
||||
public final String getTitle(Apple2Activity activity) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
// in debug mode we actually exercise the crash reporter ...
|
||||
return activity.getResources().getString(R.string.crasher_title);
|
||||
} else {
|
||||
return activity.getResources().getString(R.string.crasher_check_title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getSummary(Apple2Activity activity) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return activity.getResources().getString(R.string.crasher_summary);
|
||||
} else {
|
||||
return activity.getResources().getString(R.string.crasher_check_summary);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final Apple2Activity activity, View convertView) {
|
||||
convertView = _basicView(activity, this, convertView);
|
||||
if (!BuildConfig.DEBUG) {
|
||||
CheckBox cb = _addCheckbox(activity, this, convertView, Apple2Preferences.CRASH_CHECK.booleanValue(activity));
|
||||
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Apple2Preferences.CRASH_CHECK.saveBoolean(activity, isChecked);
|
||||
}
|
||||
});
|
||||
}
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSelection(final Apple2Activity activity, final Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
_alertDialogHandleSelection(activity, R.string.crasher, Apple2CrashHandler.CrashType.titles(activity), new IPreferenceLoadSave() {
|
||||
@Override
|
||||
public int intValue() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveInt(int value) {
|
||||
switch (value) {
|
||||
case 0: {
|
||||
final String[] str = new String[1];
|
||||
str[0] = null;
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d(TAG, "About to NPE : " + str[0].length());
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Apple2CrashHandler.getInstance().performCrash(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static final int size = SETTINGS.values().length;
|
||||
|
||||
@Override
|
||||
public void handleSelection(Apple2Activity activity, Apple2AbstractMenu settingsMenu, boolean isChecked) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(Apple2Activity activity, View convertView) {
|
||||
return _basicView(activity, this, convertView);
|
||||
}
|
||||
|
||||
public static String[] titles(Apple2Activity activity) {
|
||||
String[] titles = new String[size];
|
||||
int i = 0;
|
||||
for (SETTINGS setting : values()) {
|
||||
titles[i++] = setting.getTitle(activity);
|
||||
}
|
||||
return titles;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.deadc0de.apple2ix.basic.R;
|
||||
|
||||
public class Apple2SplashScreen implements Apple2MenuView {
|
||||
|
||||
private final static String TAG = "Apple2SplashScreen";
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private boolean mDismissable = true;
|
||||
private View mSettingsView = null;
|
||||
|
||||
public Apple2SplashScreen(Apple2Activity activity, boolean dismissable) {
|
||||
mActivity = activity;
|
||||
setup();
|
||||
setDismissable(dismissable);
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
mSettingsView = inflater.inflate(R.layout.activity_splash_screen, null, false);
|
||||
|
||||
Button startButton = (Button) mSettingsView.findViewById(R.id.startButton);
|
||||
startButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Apple2SplashScreen.this.dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
Button prefsButton = (Button) mSettingsView.findViewById(R.id.prefsButton);
|
||||
prefsButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Apple2SettingsMenu settingsMenu = mActivity.getSettingsMenu();
|
||||
settingsMenu.show();
|
||||
}
|
||||
});
|
||||
|
||||
Button disksButton = (Button) mSettingsView.findViewById(R.id.disksButton);
|
||||
disksButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Apple2DisksMenu disksMenu = mActivity.getDisksMenu();
|
||||
disksMenu.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setDismissable(boolean dismissable) {
|
||||
mDismissable = dismissable;
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Button startButton = (Button) mSettingsView.findViewById(R.id.startButton);
|
||||
startButton.setEnabled(mDismissable);
|
||||
Button prefsButton = (Button) mSettingsView.findViewById(R.id.prefsButton);
|
||||
prefsButton.setEnabled(mDismissable);
|
||||
Button disksButton = (Button) mSettingsView.findViewById(R.id.disksButton);
|
||||
disksButton.setEnabled(mDismissable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public final boolean isCalibrating() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onKeyTapCalibrationEvent(char ascii, int scancode) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (isShowing()) {
|
||||
return;
|
||||
}
|
||||
mActivity.pushApple2View(this);
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
if (mDismissable) {
|
||||
mActivity.popApple2View(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void dismissAll() {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mSettingsView.getParent() != null;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mSettingsView;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
* Apple // emulator for *nix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Sourced from AOSP "GL2JNI" sample code.
|
||||
*/
|
||||
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.Log;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import javax.microedition.khronos.egl.EGL10;
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.egl.EGLContext;
|
||||
import javax.microedition.khronos.egl.EGLDisplay;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* A simple GLSurfaceView sub-class that demonstrate how to perform
|
||||
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
|
||||
* details:
|
||||
*
|
||||
* - The class must use a custom context factory to enable 2.0 rendering.
|
||||
* See ContextFactory class definition below.
|
||||
*
|
||||
* - The class must use a custom EGLConfigChooser to be able to select
|
||||
* an EGLConfig that supports 2.0. This is done by providing a config
|
||||
* specification to eglChooseConfig() that has the attribute
|
||||
* EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag
|
||||
* set. See ConfigChooser class definition below.
|
||||
*
|
||||
* - The class must select the surface's format, then choose an EGLConfig
|
||||
* that matches it exactly (with regards to red/green/blue/alpha channels
|
||||
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
|
||||
*/
|
||||
class Apple2View extends GLSurfaceView {
|
||||
private final static String TAG = "Apple2View";
|
||||
private final static boolean DEBUG = false;
|
||||
|
||||
private Apple2Activity mActivity = null;
|
||||
private Runnable mGraphicsInitializedRunnable = null;
|
||||
|
||||
private static native void nativeGraphicsInitialized(int width, int height);
|
||||
|
||||
private static native void nativeGraphicsChanged(int width, int height);
|
||||
|
||||
private static native void nativeRender();
|
||||
|
||||
public Apple2View(Apple2Activity activity, Runnable graphicsInitializedRunnable) {
|
||||
super(activity.getApplication());
|
||||
mActivity = activity;
|
||||
mGraphicsInitializedRunnable = graphicsInitializedRunnable;
|
||||
|
||||
/* By default, GLSurfaceView() creates a RGB_565 opaque surface.
|
||||
* If we want a translucent one, we should change the surface's
|
||||
* format here, using PixelFormat.TRANSLUCENT for GL Surfaces
|
||||
* is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
|
||||
*/
|
||||
this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
|
||||
|
||||
/* Setup the context factory for 2.0 rendering.
|
||||
* See ContextFactory class definition below
|
||||
*/
|
||||
setEGLContextFactory(new ContextFactory());
|
||||
|
||||
/* We need to choose an EGLConfig that matches the format of
|
||||
* our surface exactly. This is going to be done in our
|
||||
* custom config chooser. See ConfigChooser class definition
|
||||
* below.
|
||||
*/
|
||||
setEGLConfigChooser(new ConfigChooser(8, 8, 8, 8, /*depth:*/0, /*stencil:*/0));
|
||||
|
||||
/* Set the renderer responsible for frame rendering */
|
||||
setRenderer(new Renderer());
|
||||
|
||||
// Another Android Annoyance ...
|
||||
// Even though we no longer use the system soft keyboard (which would definitely trigger width/height changes to our OpenGL canvas),
|
||||
// we still need to listen to dimension changes, because it seems on some janky devices you have an incorrect width/height set when
|
||||
// the initial OpenGL onSurfaceChanged() callback occurs. For now, include this defensive coding...
|
||||
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
public void onGlobalLayout() {
|
||||
Rect rect = new Rect();
|
||||
Apple2View.this.getWindowVisibleDisplayFrame(rect);
|
||||
int h = rect.height();
|
||||
int w = rect.width();
|
||||
if (w < h) {
|
||||
// assure landscape dimensions
|
||||
final int w_ = w;
|
||||
w = h;
|
||||
h = w_;
|
||||
}
|
||||
nativeGraphicsChanged(w, h);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
|
||||
private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
||||
|
||||
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
|
||||
Log.w(TAG, "creating OpenGL ES 2.0 context");
|
||||
checkEglError("Before eglCreateContext", egl);
|
||||
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
|
||||
EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
|
||||
checkEglError("After eglCreateContext", egl);
|
||||
return context;
|
||||
}
|
||||
|
||||
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
|
||||
egl.eglDestroyContext(display, context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkEglError(String prompt, EGL10 egl) {
|
||||
int error;
|
||||
while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
|
||||
Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
|
||||
|
||||
public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
|
||||
mRedSize = r;
|
||||
mGreenSize = g;
|
||||
mBlueSize = b;
|
||||
mAlphaSize = a;
|
||||
mDepthSize = depth;
|
||||
mStencilSize = stencil;
|
||||
}
|
||||
|
||||
/* This EGL config specification is used to specify 2.0 rendering.
|
||||
* We use a minimum size of 4 bits for red/green/blue, but will
|
||||
* perform actual matching in chooseConfig() below.
|
||||
*/
|
||||
private static int EGL_OPENGL_ES2_BIT = 4;
|
||||
private static int[] s_configAttribs2 = {
|
||||
EGL10.EGL_RED_SIZE, 4,
|
||||
EGL10.EGL_GREEN_SIZE, 4,
|
||||
EGL10.EGL_BLUE_SIZE, 4,
|
||||
EGL10.EGL_ALPHA_SIZE, 4,
|
||||
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||
EGL10.EGL_NONE
|
||||
};
|
||||
|
||||
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
|
||||
|
||||
// Get the number of minimally matching EGL configurations
|
||||
int[] num_config = new int[1];
|
||||
egl.eglChooseConfig(display, s_configAttribs2, null, 0, num_config);
|
||||
|
||||
int numConfigs = num_config[0];
|
||||
|
||||
if (numConfigs <= 0) {
|
||||
throw new IllegalArgumentException("No configs match configSpec");
|
||||
}
|
||||
|
||||
// Allocate then read the array of minimally matching EGL configs
|
||||
EGLConfig[] configs = new EGLConfig[numConfigs];
|
||||
egl.eglChooseConfig(display, s_configAttribs2, configs, numConfigs, num_config);
|
||||
|
||||
if (DEBUG) {
|
||||
printConfigs(egl, display, configs);
|
||||
}
|
||||
|
||||
// Now return the "best" one
|
||||
EGLConfig best = chooseConfig(egl, display, configs);
|
||||
if (best == null) {
|
||||
Log.e(TAG, "OOPS! Did not pick an EGLConfig. What device are you using?! Android will now crash this app...");
|
||||
} else {
|
||||
Log.w(TAG, "Using EGL CONFIG : ");
|
||||
printConfig(egl, display, best);
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
|
||||
for (EGLConfig config : configs) {
|
||||
int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
|
||||
int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
|
||||
|
||||
// We need at least mDepthSize and mStencilSize bits
|
||||
if (d < mDepthSize || s < mStencilSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We want an *exact* match for red/green/blue/alpha
|
||||
int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
|
||||
int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
|
||||
int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
|
||||
int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
|
||||
|
||||
if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) {
|
||||
|
||||
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
|
||||
return mValue[0];
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private void printConfigs(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
|
||||
int numConfigs = configs.length;
|
||||
Log.w(TAG, String.format("%d configurations", numConfigs));
|
||||
for (int i = 0; i < numConfigs; i++) {
|
||||
Log.w(TAG, String.format("Configuration %d:\n", i));
|
||||
printConfig(egl, display, configs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void printConfig(EGL10 egl, EGLDisplay display, EGLConfig config) {
|
||||
int[] attributes = {
|
||||
EGL10.EGL_BUFFER_SIZE,
|
||||
EGL10.EGL_ALPHA_SIZE,
|
||||
EGL10.EGL_BLUE_SIZE,
|
||||
EGL10.EGL_GREEN_SIZE,
|
||||
EGL10.EGL_RED_SIZE,
|
||||
EGL10.EGL_DEPTH_SIZE,
|
||||
EGL10.EGL_STENCIL_SIZE,
|
||||
EGL10.EGL_CONFIG_CAVEAT,
|
||||
EGL10.EGL_CONFIG_ID,
|
||||
EGL10.EGL_LEVEL,
|
||||
EGL10.EGL_MAX_PBUFFER_HEIGHT,
|
||||
EGL10.EGL_MAX_PBUFFER_PIXELS,
|
||||
EGL10.EGL_MAX_PBUFFER_WIDTH,
|
||||
EGL10.EGL_NATIVE_RENDERABLE,
|
||||
EGL10.EGL_NATIVE_VISUAL_ID,
|
||||
EGL10.EGL_NATIVE_VISUAL_TYPE,
|
||||
0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
|
||||
EGL10.EGL_SAMPLES,
|
||||
EGL10.EGL_SAMPLE_BUFFERS,
|
||||
EGL10.EGL_SURFACE_TYPE,
|
||||
EGL10.EGL_TRANSPARENT_TYPE,
|
||||
EGL10.EGL_TRANSPARENT_RED_VALUE,
|
||||
EGL10.EGL_TRANSPARENT_GREEN_VALUE,
|
||||
EGL10.EGL_TRANSPARENT_BLUE_VALUE,
|
||||
0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
|
||||
0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
|
||||
0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
|
||||
0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
|
||||
EGL10.EGL_LUMINANCE_SIZE,
|
||||
EGL10.EGL_ALPHA_MASK_SIZE,
|
||||
EGL10.EGL_COLOR_BUFFER_TYPE,
|
||||
EGL10.EGL_RENDERABLE_TYPE,
|
||||
0x3042 // EGL10.EGL_CONFORMANT
|
||||
};
|
||||
String[] names = {
|
||||
"EGL_BUFFER_SIZE",
|
||||
"EGL_ALPHA_SIZE",
|
||||
"EGL_BLUE_SIZE",
|
||||
"EGL_GREEN_SIZE",
|
||||
"EGL_RED_SIZE",
|
||||
"EGL_DEPTH_SIZE",
|
||||
"EGL_STENCIL_SIZE",
|
||||
"EGL_CONFIG_CAVEAT",
|
||||
"EGL_CONFIG_ID",
|
||||
"EGL_LEVEL",
|
||||
"EGL_MAX_PBUFFER_HEIGHT",
|
||||
"EGL_MAX_PBUFFER_PIXELS",
|
||||
"EGL_MAX_PBUFFER_WIDTH",
|
||||
"EGL_NATIVE_RENDERABLE",
|
||||
"EGL_NATIVE_VISUAL_ID",
|
||||
"EGL_NATIVE_VISUAL_TYPE",
|
||||
"EGL_PRESERVED_RESOURCES",
|
||||
"EGL_SAMPLES",
|
||||
"EGL_SAMPLE_BUFFERS",
|
||||
"EGL_SURFACE_TYPE",
|
||||
"EGL_TRANSPARENT_TYPE",
|
||||
"EGL_TRANSPARENT_RED_VALUE",
|
||||
"EGL_TRANSPARENT_GREEN_VALUE",
|
||||
"EGL_TRANSPARENT_BLUE_VALUE",
|
||||
"EGL_BIND_TO_TEXTURE_RGB",
|
||||
"EGL_BIND_TO_TEXTURE_RGBA",
|
||||
"EGL_MIN_SWAP_INTERVAL",
|
||||
"EGL_MAX_SWAP_INTERVAL",
|
||||
"EGL_LUMINANCE_SIZE",
|
||||
"EGL_ALPHA_MASK_SIZE",
|
||||
"EGL_COLOR_BUFFER_TYPE",
|
||||
"EGL_RENDERABLE_TYPE",
|
||||
"EGL_CONFORMANT"
|
||||
};
|
||||
int[] value = new int[1];
|
||||
for (int i = 0; i < attributes.length; i++) {
|
||||
int attribute = attributes[i];
|
||||
String name = names[i];
|
||||
if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
|
||||
Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
|
||||
} else {
|
||||
// Log.w(TAG, String.format(" %s: failed\n", name));
|
||||
while (egl.eglGetError() != EGL10.EGL_SUCCESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subclasses can adjust these values:
|
||||
protected int mRedSize;
|
||||
protected int mGreenSize;
|
||||
protected int mBlueSize;
|
||||
protected int mAlphaSize;
|
||||
protected int mDepthSize;
|
||||
protected int mStencilSize;
|
||||
private int[] mValue = new int[1];
|
||||
}
|
||||
|
||||
private class Renderer implements GLSurfaceView.Renderer {
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 gl) {
|
||||
nativeRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||
Apple2Preferences.GL_VENDOR.saveString(mActivity, GLES20.glGetString(GLES20.GL_VENDOR));
|
||||
Apple2Preferences.GL_RENDERER.saveString(mActivity, GLES20.glGetString(GLES20.GL_RENDERER));
|
||||
Apple2Preferences.GL_VERSION.saveString(mActivity, GLES20.glGetString(GLES20.GL_VERSION));
|
||||
|
||||
Log.v(TAG, "graphicsInitialized(" + width + ", " + height + ")");
|
||||
|
||||
if (width < height) {
|
||||
// assure landscape dimensions
|
||||
final int w_ = width;
|
||||
width = height;
|
||||
height = w_;
|
||||
}
|
||||
|
||||
nativeGraphicsInitialized(width, height);
|
||||
|
||||
if (Apple2View.this.mGraphicsInitializedRunnable != null) {
|
||||
Apple2View.this.mGraphicsInitializedRunnable.run();
|
||||
Apple2View.this.mGraphicsInitializedRunnable = null;
|
||||
}
|
||||
|
||||
Apple2View.this.mActivity.maybeResumeCPU();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013-2014 Igor Zinken - http://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package org.deadc0de.apple2ix;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* Created by IntelliJ IDEA.
|
||||
* User: igorzinken
|
||||
* Date: 03-06-13
|
||||
* Time: 13:16
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
public final class DevicePropertyCalculator
|
||||
{
|
||||
public final static int defaultSampleRate = 22050;
|
||||
|
||||
public static boolean detectLowLatency( Context aContext )
|
||||
{
|
||||
// check for low latency audio
|
||||
PackageManager pm = aContext.getPackageManager();
|
||||
|
||||
return pm.hasSystemFeature( PackageManager.FEATURE_AUDIO_LOW_LATENCY );
|
||||
}
|
||||
|
||||
/*
|
||||
Beginning with API level 17 (Android platform version 4.2), an application can query for the native or optimal
|
||||
output sample rate and buffer size for the device's primary output stream. When combined with the feature test
|
||||
just mentioned, an app can now configure itself appropriately for lower latency output on devices that claim support.
|
||||
|
||||
The recommended sequence is:
|
||||
|
||||
Check for API level 9 or higher, to confirm use of OpenSL ES.
|
||||
Check for feature "android.hardware.audio.low_latency" using code such as this:
|
||||
import android.content.pm.PackageManager;
|
||||
...
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
|
||||
Check for API level 17 or higher, to confirm use of android.media.AudioManager.getProperty().
|
||||
Get the native or optimal output sample rate and buffer size for this device's primary output stream, using code such as this:
|
||||
import android.media.AudioManager;
|
||||
...
|
||||
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE));
|
||||
String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER));
|
||||
Note that sampleRate and framesPerBuffer are Strings. First check for null and then convert to int using Integer.parseInt().
|
||||
Now use OpenSL ES to create an AudioPlayer with PCM buffer queue data locator.
|
||||
The number of lower latency audio players is limited. If your application requires more than a few audio sources, consider mixing your audio at application level. Be sure to destroy your audio players when your activity is paused, as they are a global resource shared with other apps.
|
||||
*/
|
||||
public static int getRecommendedSampleRate( Context aContext )
|
||||
{
|
||||
String SR_CHECK = null;
|
||||
|
||||
// API level 17 available ?
|
||||
if ( android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 )
|
||||
{
|
||||
AudioManager am = ( AudioManager ) aContext.getSystemService( Context.AUDIO_SERVICE );
|
||||
|
||||
// Use the sample rate provided by AudioManager.getProperty(PROPERTY_OUTPUT_SAMPLE_RATE).
|
||||
// Otherwise your buffers take a detour through the system resampler.
|
||||
|
||||
SR_CHECK = am.getProperty( AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE );
|
||||
}
|
||||
|
||||
return ( SR_CHECK != null ) ? Integer.parseInt( SR_CHECK ) : defaultSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the recommended buffer size for the device running the application
|
||||
* you can increase / decrease the buffer size for lower latency or higher stability, but
|
||||
* note you must use multiples of this recommendation !! Otherwise the buffer callback will
|
||||
* occasionally get two calls per timeslice which can cause glitching unless CPU usage is
|
||||
* really light
|
||||
*
|
||||
* some measurements ( combined w/ recommended sample rate above ):
|
||||
*
|
||||
* Samsung Galaxy S Plus on 2.3 Gingerbread : 4800 samples per buffer ( 44.1 kHz ) ADEQUATE ( 75 samples == SOMEWHAT STABLE
|
||||
* ( unless under heavy stress ), 150 == PERFECT )
|
||||
* Samsung Galaxy Nexus 4.2.1 Jelly Bean : 144 samples per buffer ( 44.1 kHz ) INADEQUATE ( 288 samples == OK )
|
||||
* Samsung Galaxy S3 4.1.2 Jelly Bean : 2048 samples per buffer ( ---- kHz ) ADEQUATE ( 512 samples == OK )
|
||||
* Asus Nexus 7 on 4.2.2 Jelly Bean : 512 samples per buffer ( 44.1 kHz ) ACCURATE! ( perhaps 384 ?? )
|
||||
* HTC One V 4.0.3 Ice Cream Sandwich : 4800 samples per buffer ( 44.1 kHz ) ADEQUATE ( 300 samples == OK )
|
||||
*
|
||||
* @param aContext {Context}
|
||||
* @return {int}
|
||||
*/
|
||||
public static int getRecommendedBufferSize( Context aContext, boolean isStereo)
|
||||
{
|
||||
// prepare Native Audio engine
|
||||
String BS_CHECK = null;
|
||||
|
||||
// API level 17 available ?
|
||||
if ( android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 )
|
||||
{
|
||||
AudioManager am = ( AudioManager ) aContext.getSystemService( Context.AUDIO_SERVICE );
|
||||
|
||||
BS_CHECK = am.getProperty( AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER );
|
||||
}
|
||||
return ( BS_CHECK != null ) ? Integer.parseInt( BS_CHECK ) : AudioTrack.getMinBufferSize( getRecommendedSampleRate( aContext ),
|
||||
isStereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT );
|
||||
}
|
||||
|
||||
public static int getMinimumBufferSize( Context aContext, boolean isStereo )
|
||||
{
|
||||
// minimum buffer size we allow is the recommendation divided by four
|
||||
int min = DevicePropertyCalculator.getRecommendedBufferSize( aContext, isStereo ) / 4;
|
||||
|
||||
// however, we'd like to supply tha rea of 64 samples per buffer as an option
|
||||
while ( min > 128 ) min /= 2; // 128 as we do a greater than check
|
||||
|
||||
// but the minimum we allow is 32 samples per buffer
|
||||
if ( min < 32 )
|
||||
min *= 2;
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public static int getMaximumBufferSize( Context aContext, boolean isStereo )
|
||||
{
|
||||
int max = DevicePropertyCalculator.getRecommendedBufferSize( aContext, isStereo ) * 8;
|
||||
|
||||
// nothing TOO extravagant... 8192 should be enough...
|
||||
while ( max > 10000 )
|
||||
max /= 2;
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
../../../libs
|
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 197 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 358 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 358 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 494 KiB |
After Width: | Height: | Size: 6.9 KiB |
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2006 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.
|
||||
-->
|
||||
|
||||
<!-- Modified from Gingerbread android.R.layout.preference -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_marginBottom="6dip"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView android:id="@+id/a2disk_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal" />
|
||||
|
||||
<!--
|
||||
<TextView android:id="@+id/a2disk_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/a2preference_title"
|
||||
android:layout_alignLeft="@id/a2preference_title"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:maxLines="4" />
|
||||
-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Custom preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@+id/a2disk_widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:descendantFocusability="blocksDescendants" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@color/black"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton_diskA"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/diskA" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton_diskB"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/diskB" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton_readOnly"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/disk_read_only" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radioButton_readWrite"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/disk_read_write" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2006 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.
|
||||
-->
|
||||
|
||||
<!-- Modified from Gingerbread android.R.layout.preference -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="6dip"
|
||||
android:layout_marginBottom="6dip"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView android:id="@+id/a2preference_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal" />
|
||||
|
||||
<TextView android:id="@+id/a2preference_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/a2preference_title"
|
||||
android:layout_alignLeft="@id/a2preference_title"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:maxLines="4" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Custom preference should place its actual preference widget here. -->
|
||||
<LinearLayout android:id="@+id/a2preference_widget_frame"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:descendantFocusability="blocksDescendants" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/preference_margin_left"
|
||||
android:layout_marginStart="@dimen/preference_margin_left"
|
||||
android:layout_marginRight="@dimen/preference_margin_right"
|
||||
android:layout_marginEnd="@dimen/preference_margin_right"
|
||||
android:layout_marginTop="@dimen/preference_margin_top"
|
||||
android:layout_marginBottom="@dimen/preference_margin_bottom"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView android:id="@+id/a2preference_slider_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="Lorem ipsum dolor sit amet"
|
||||
android:maxLines="4" />
|
||||
|
||||
<LinearLayout android:id="@+id/a2preference_slider_widgets"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/a2preference_slider_summary"
|
||||
android:layout_alignLeft="@id/a2preference_slider_summary"
|
||||
android:layout_alignStart="@id/a2preference_slider_summary"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:descendantFocusability="blocksDescendants">
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/a2preference_slider_seekBar"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/a2preference_slider_seekBarValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/preference_margin"
|
||||
android:layout_marginStart="@dimen/preference_margin"
|
||||
android:gravity="center"
|
||||
android:text="0" />
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginRight="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#00ff0000">
|
||||
|
||||
<SeekBar
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/seekBar"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginStart="0dp" />
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginLeft="0dp"
|
||||
android:layout_marginRight="0dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="#00ff0000">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/currentChoicePrompt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginLeft="@dimen/preference_margin_left"
|
||||
android:layout_marginStart="@dimen/preference_margin_left"
|
||||
android:layout_marginTop="@dimen/preference_margin_top"
|
||||
android:text="@string/keypad_choose_current" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/skipButton"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginEnd="@dimen/preference_margin_right"
|
||||
android:layout_marginRight="@dimen/preference_margin_right"
|
||||
android:layout_marginTop="@dimen/preference_margin_top"
|
||||
android:text="@string/skip" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical"
|
||||
android:baselineAligned="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
|
||||
<LinearLayout
|
||||
android:background="@color/black"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_disks"
|
||||
style="?android:attr/listSeparatorTextViewStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/header_disks" />
|
||||
|
||||
<Button
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:drawableStart="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:id="@+id/cancelButton" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/listView_settings"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,27 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main_popup_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@android:color/background_light"
|
||||
android:orientation="vertical">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="2dp"
|
||||
android:background="@android:color/darker_gray">
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_margin="2dp">
|
||||
<ListView
|
||||
android:id="@+id/main_popup_menu"
|
||||
android:background="@color/black"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent">
|
||||
</ListView>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@color/black"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/black"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_settings"
|
||||
style="?android:attr/listSeparatorTextViewStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/settings" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableLeft="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:drawableStart="@android:drawable/ic_menu_close_clear_cancel" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/listView_settings"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/splashScreen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/preference_margin_left"
|
||||
android:layout_marginStart="@dimen/preference_margin_left"
|
||||
android:layout_marginRight="@dimen/preference_margin_right"
|
||||
android:layout_marginEnd="@dimen/preference_margin_right"
|
||||
android:layout_marginTop="@dimen/preference_margin_top"
|
||||
android:layout_marginBottom="@dimen/preference_margin_bottom"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/apple_iie"
|
||||
android:id="@+id/splashView" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/imageView"
|
||||
android:src="@drawable/ic_launcher"
|
||||
android:layout_alignTop="@+id/startButton"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/emulation_continue"
|
||||
android:id="@+id/startButton"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginTop="10dp" />
|
||||
|
||||
<Button
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:text="@string/emulation_settings"
|
||||
android:id="@+id/prefsButton"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_below="@+id/startButton"
|
||||
android:layout_alignRight="@+id/startButton"
|
||||
android:layout_alignEnd="@+id/startButton"
|
||||
android:layout_alignLeft="@+id/startButton"
|
||||
android:layout_alignStart="@+id/startButton" />
|
||||
|
||||
<Button
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:text="@string/emulation_disks"
|
||||
android:id="@+id/disksButton"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_below="@+id/prefsButton"
|
||||
android:layout_alignRight="@+id/startButton"
|
||||
android:layout_alignEnd="@+id/startButton"
|
||||
android:layout_alignLeft="@+id/startButton"
|
||||
android:layout_alignStart="@+id/startButton" />
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:visibility="invisible"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/crash_progressBar"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignLeft="@+id/imageView"
|
||||
android:layout_alignStart="@+id/imageView" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,6 @@
|
|||
<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>
|
|
@ -0,0 +1,10 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="preference_margin">6dp</dimen>
|
||||
<dimen name="preference_margin_left">15dp</dimen>
|
||||
<dimen name="preference_margin_right">6dp</dimen>
|
||||
<dimen name="preference_margin_top">6dp</dimen>
|
||||
<dimen name="preference_margin_bottom">6dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,188 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<color name="black">#000000</color>
|
||||
<color name="white">#ffffff</color>
|
||||
|
||||
<string name="about_apple2ix">About Apple2ix…</string>
|
||||
<string name="about_apple2ix_summary">About this software</string>
|
||||
<string name="about_apple2">About Apple //e…</string>
|
||||
<string name="about_apple2_summary">More information about the Apple //e computer</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="app_name">Apple2ix</string>
|
||||
<string name="audio_configure">Configure audio…</string>
|
||||
<string name="audio_configure_summary">Speaker volume, Mockingboard, etc</string>
|
||||
<string name="audio_latency">Audio latency</string>
|
||||
<string name="audio_latency_summary">Audio latency in secs</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="color_bw">Black/white</string>
|
||||
<string name="color_color">Color</string>
|
||||
<string name="color_interpolated">Interpolated color</string>
|
||||
<string name="crasher">Crasher</string>
|
||||
<string name="crasher_check_title">Check for crash reports</string>
|
||||
<string name="crasher_check_summary">Check for crash reports to email developer)</string>
|
||||
<string name="crasher_processing">Processing…</string>
|
||||
<string name="crasher_processing_message">Processing crash reports…</string>
|
||||
<string name="crasher_send">Send crash report?</string>
|
||||
<string name="crasher_send_message">Sorry there has been a problem. Do you want to send a crash report to the developer?</string>
|
||||
<string name="crasher_summary">Test crash generation</string>
|
||||
<string name="crasher_title">Crash emulator</string>
|
||||
<string name="crash_null">NULL-deref</string>
|
||||
<string name="crash_java_npe">Java NPE</string>
|
||||
<string name="crash_stackcall_overflow">stack call overflow</string>
|
||||
<string name="crash_stackbuf_overflow">stack buffer overflow</string>
|
||||
<string name="diskA">Drive 1</string>
|
||||
<string name="diskB">Drive 2</string>
|
||||
<string name="disk_eject">Eject</string>
|
||||
<string name="disk_insert_toast">Inserted disk in drive 1 read-only</string>
|
||||
<string name="disk_insert_could_not_read">OOPS, could not read the disk image!</string>
|
||||
<string name="disk_read_only">Read only</string>
|
||||
<string name="disk_read_write">Read/write</string>
|
||||
<string name="emulation_continue">Continue…</string>
|
||||
<string name="emulation_settings">Settings…</string>
|
||||
<string name="emulation_disks">Load disk image…</string>
|
||||
<string name="header_disks">Insert disk:</string>
|
||||
<string name="input_configure">Configure input devices…</string>
|
||||
<string name="input_configure_summary">Keyboard, joystick, etc</string>
|
||||
<string name="input_current">Current touch device</string>
|
||||
<string name="input_current_summary">Choose current touch device</string>
|
||||
<string name="input_first_joystick">Touch joystick</string>
|
||||
<string name="input_first_keyboard">Touch keyboard</string>
|
||||
<string name="joystick">Joystick</string>
|
||||
<string name="joystickA">Touch Joystick</string>
|
||||
<string name="joystick_axis_sensitivity_summary">Tune joystick axis sensitivity (decelerate or accelerate)</string>
|
||||
<string name="joystick_button_axis_enable">Enable button axis</string>
|
||||
<string name="joystick_button_axis_enable_summary">Enable button axis</string>
|
||||
<string name="joystick_button_button1">Button1 (Open Apple)</string>
|
||||
<string name="joystick_button_button2">Button2 (Closed Apple)</string>
|
||||
<string name="joystick_button_button_both">Both</string>
|
||||
<string name="joystick_button_button_none">None</string>
|
||||
<string name="joystick_button_tap_button">Tap fire</string>
|
||||
<string name="joystick_button_tap_button_summary">Button to fire on tap down</string>
|
||||
<string name="joystick_button_tapdelay_summary">Joystick button tap delay in secs</string>
|
||||
<string name="joystick_button_swipe_up_button">Swipe up fire</string>
|
||||
<string name="joystick_button_swipe_up_button_summary">Button to fire on swipe up</string>
|
||||
<string name="joystick_button_swipe_down_button">Swipe down fire</string>
|
||||
<string name="joystick_button_swipe_down_button_summary">Button to fire on swipe down</string>
|
||||
<string name="joystick_button_threshold_summary">Joystick/keypad button switch threshold in pts</string>
|
||||
<string name="joystick_calibrate">Calibrate…</string>
|
||||
<string name="joystick_calibrate_summary">Configure and test current settings</string>
|
||||
<string name="joystick_configure">Configure joystick…</string>
|
||||
<string name="joystick_configure_summary">Axis touch, buttons, etc</string>
|
||||
<string name="joystick_current">Current joystick flavor</string>
|
||||
<string name="joystick_current_summary">Emulated physical joystick or keypad</string>
|
||||
<string name="joystick_axisleft">Joystick/keypad axis on left</string>
|
||||
<string name="joystick_axisleft_summary">Joystick/keypad axis on left (buttons on right)</string>
|
||||
<string name="joystick_visible">Joystick/keypad visibility</string>
|
||||
<string name="joystick_visible_summary">Show controls overlay when engaged</string>
|
||||
<string name="key_closed_apple">[ClosedApple]</string>
|
||||
<string name="key_ctrl">[Ctrl]</string>
|
||||
<string name="key_down">↓</string>
|
||||
<string name="key_esc">[ESC]</string>
|
||||
<string name="key_left">←</string>
|
||||
<string name="key_none">[None]</string>
|
||||
<string name="key_open_apple">[OpenApple]</string>
|
||||
<string name="key_ret">[Return]</string>
|
||||
<string name="key_right">→</string>
|
||||
<string name="key_space">[Space]</string>
|
||||
<string name="key_up">↑</string>
|
||||
<string name="keyboard">Keyboard</string>
|
||||
<string name="keyboard_choose_alt">Choose alt keyboard…</string>
|
||||
<string name="keyboard_choose_alt_summary">Choose alternative customized layout</string>
|
||||
<string name="keyboard_click_enabled">Enable key click</string>
|
||||
<string name="keyboard_click_enabled_summary">Enables key click sound if available</string>
|
||||
<string name="keyboard_configure">Configure keyboard…</string>
|
||||
<string name="keyboard_configure_summary">Transparency, lowercase, custom keys</string>
|
||||
<string name="keyboard_lowercase_enabled">Enable lowercase</string>
|
||||
<string name="keyboard_lowercase_enabled_summary">Enable lowercase keys</string>
|
||||
<string name="keyboard_preset_default">Default</string>
|
||||
<string name="keyboard_visibility_active">Visibility when active</string>
|
||||
<string name="keyboard_visibility_active_summary">Keyboard visibility when active</string>
|
||||
<string name="keyboard_visibility_inactive">Visibility when inactive</string>
|
||||
<string name="keyboard_visibility_inactive_summary">Keyboard visibility when inactive</string>
|
||||
<string name="keypad">Keypad Joystick</string>
|
||||
<string name="keypadA">Touch Keypad Joystick</string>
|
||||
<string name="keypad_button_tap_button">Tap key:</string>
|
||||
<string name="keypad_button_swipe_up_button">Swipe up key:</string>
|
||||
<string name="keypad_button_swipe_down_button">Swipe down key:</string>
|
||||
<string name="keypad_calibrate">Calibrate…</string>
|
||||
<string name="keypad_calibrate_summary">Configure and test current settings</string>
|
||||
<string name="keypad_choose">Choose keypad keys…</string>
|
||||
<string name="keypad_choose_summary">Choose axis and button keys</string>
|
||||
<string name="keypad_choose_title">Axis & buttons</string>
|
||||
<string name="keypad_choose_current">Choose XXX Key: </string>
|
||||
<string name="keypad_chosen_keys">I,J,K,M [Space]</string>
|
||||
<string name="keypad_configure">Configure keypad joystick…</string>
|
||||
<string name="keypad_configure_summary">@string/joystick_configure_summary</string>
|
||||
<string name="keypad_key_axis_c">Center</string>
|
||||
<string name="keypad_key_axis_dn">Down</string>
|
||||
<string name="keypad_key_axis_dl">Down and Left</string>
|
||||
<string name="keypad_key_axis_dr">Down and Right</string>
|
||||
<string name="keypad_key_axis_l">Left</string>
|
||||
<string name="keypad_key_axis_r">Right</string>
|
||||
<string name="keypad_key_axis_ul">Up and Left</string>
|
||||
<string name="keypad_key_axis_up">Up</string>
|
||||
<string name="keypad_key_axis_ur">Up and Right</string>
|
||||
<string name="keypad_key_button_tap">Tap</string>
|
||||
<string name="keypad_key_button_swipeup">Swipe Up</string>
|
||||
<string name="keypad_key_button_swipedown">Swipe Down</string>
|
||||
<string name="keypad_preset_crazy_seafox">Seafox keys ;-)…</string>
|
||||
<string name="keypad_preset_custom">Choose custom…</string>
|
||||
<string name="keypad_preset_arrows_space">↑,←,→,↓, tap spacebar</string>
|
||||
<string name="keypad_preset_az_left_right_space">A,Z,←,→, tap spacebar</string>
|
||||
<string name="keypad_preset_ijkl_uo">I,J,L,K, tap U, swipe down O</string>
|
||||
<string name="keypad_preset_ijkm_space">I,J,K,M, tap spacebar</string>
|
||||
<string name="keypad_preset_left_right_space">←,→, tap spacebar</string>
|
||||
<string name="keypad_preset_wadx_space">W,A,D,X, tap spacebar</string>
|
||||
<string name="keypad_repeat_summary">Key repeat threshold in secs</string>
|
||||
<string name="max">Max</string>
|
||||
<string name="menu_disks">Load disk image…</string>
|
||||
<string name="menu_disks_summary">Insert a Disk ][ image file</string>
|
||||
<string name="menu_settings">Emulator settings…</string>
|
||||
<string name="menu_settings_summary">General, CPU, Joystick</string>
|
||||
<string name="mockingboard_disabled_title">Mockingboard disabled</string>
|
||||
<string name="mockingboard_disabled_mesg">Mockingboard could not be enabled</string>
|
||||
<string name="mockingboard_enable">Enable Mockingboard</string>
|
||||
<string name="mockingboard_enable_summary">Revision C in Slot 4/5 (may require restart)</string>
|
||||
<string name="mockingboard_volume">Mockingboard volume</string>
|
||||
<string name="mockingboard_volume_summary">Set the Mockingboard(s) volume</string>
|
||||
<string name="no">No</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="preferences_reset_title">Reset preferences</string>
|
||||
<string name="preferences_reset_summary">Reset preferences to defaults</string>
|
||||
<string name="preferences_reset_really">Really reset?</string>
|
||||
<string name="preferences_reset_warning">You will lose your settings</string>
|
||||
<string name="quit">Quit emulator…</string>
|
||||
<string name="quit_summary"></string>
|
||||
<string name="quit_really">Quit emulator?</string>
|
||||
<string name="quit_warning">You will lose unsaved progress</string>
|
||||
<string name="reboot">Reboot emulator…</string>
|
||||
<string name="reboot_really">Reboot emulator?</string>
|
||||
<string name="reboot_summary"></string>
|
||||
<string name="reboot_warning">You will lose unsaved progress</string>
|
||||
<string name="skip">Skip→</string>
|
||||
<string name="spacer"></string>
|
||||
<string name="speaker_disabled_title">Speaker disabled</string>
|
||||
<string name="speaker_disabled_mesg">Speaker could not be enabled</string>
|
||||
<string name="speaker_enable">Enable speaker</string>
|
||||
<string name="speaker_enable_summary">(Speaker cannot be disabled)</string>
|
||||
<string name="speaker_volume">Speaker volume</string>
|
||||
<string name="speaker_volume_summary">Set the speaker volume</string>
|
||||
<string name="speed_alt">Alternate CPU speed</string>
|
||||
<string name="speed_cpu">CPU Speed</string>
|
||||
<string name="settings">Apple2ix emulator settings</string>
|
||||
<string name="settings_audio">Apple2ix audio settings</string>
|
||||
<string name="settings_advanced">Advanced settings</string>
|
||||
<string name="settings_advanced_summary">Warning: these settings may potentially degrade emulation performance</string>
|
||||
<string name="settings_advanced_joystick">Advanced joystick/keypad settings</string>
|
||||
<string name="settings_advanced_joystick_summary">Advanced settings and performance tuning</string>
|
||||
<string name="tab_general">General</string>
|
||||
<string name="tab_joystick">Joystick</string>
|
||||
<string name="touch_menu_enable">Enable touch menus</string>
|
||||
<string name="touch_menu_enable_summary">Enables soft menu buttons in top screen corners</string>
|
||||
<string name="touch_menu_visibility">Touch menu visibility</string>
|
||||
<string name="touch_menu_visibility_summary">Touch menu visibility when inactive</string>
|
||||
<string name="video_configure">Configure video…</string>
|
||||
<string name="video_configure_summary">Color settings</string>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<!-- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> ... this freaks-out CLI builds -->
|
||||
<!-- Customize your theme here. -->
|
||||
<!-- </style> -->
|
||||
|
||||
</resources>
|
|
@ -0,0 +1 @@
|
|||
../../../apple2-images-pub/disks
|
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
"Default Alternate Touch Keyboard",
|
||||
|
||||
{
|
||||
"_comment" : "hex code for special glyphs",
|
||||
"_AA" : "b5",
|
||||
"_CTRL": "b3",
|
||||
"_XX" : "9b",
|
||||
"_ESC" : "bc",
|
||||
"_OA" : "81",
|
||||
"_CA" : "80",
|
||||
"_UP" : "8b",
|
||||
"_LT" : "88",
|
||||
"_RT" : "95",
|
||||
"_DN" : "8a"
|
||||
},
|
||||
|
||||
["reserved for future use"],
|
||||
["reserved for future use"],
|
||||
["_AA", "", "_CTRL", "", "_ESC", "", "_OA", "", "_CA", ""],
|
||||
[ "", "", "", "", "", "", "", "", "", ""],
|
||||
[ "", "", "", "", "", "_UP", "", "", "", ""],
|
||||
[ "", "", "", "", "_LT", "_XX", "_RT", "", "", ""],
|
||||
[ "", "", "", "", "", "_DN", "", "", "", ""],
|
||||
[ "", "", "", "", "", "", "", "", "", ""]
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
"Alt keyboard optimized for Conan",
|
||||
|
||||
{
|
||||
"_comment" : "hex code for special glyphs",
|
||||
"_AA" : "b5",
|
||||
"_LT" : "88",
|
||||
"_RT" : "95",
|
||||
"_SP" : "b1"
|
||||
},
|
||||
|
||||
[ "reserved for future use" ],
|
||||
[ "reserved for future use" ],
|
||||
[ "_AA", "", "", "", "", "", "", "", "", "" ],
|
||||
[ "", "", "", "", "", "", "", "", "", "" ],
|
||||
[ "", "", "", "", "", "", "", "", "", "" ],
|
||||
[ "Q", "", "", "", "", "", "", "", "A", "" ],
|
||||
[ "", "", "", "", "", "", "", "_LT", "", "_RT" ],
|
||||
[ "_SP", "", "", "", "", "", "", "", "Z", "" ]
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
[
|
||||
"Alt keyboard optimized for Ultima(tm) 4",
|
||||
|
||||
{
|
||||
"_comment" : "hex code for special glyphs",
|
||||
"_AA" : "b5",
|
||||
"_ESC" : "bc",
|
||||
"_UP" : "8b",
|
||||
"_LT" : "88",
|
||||
"_RT" : "95",
|
||||
"_DN" : "8a",
|
||||
"_SP" : "b1"
|
||||
},
|
||||
|
||||
["reserved for future use"],
|
||||
["reserved for future use"],
|
||||
["_AA", "", "", "", "", "", "1", "2", "3", "4"],
|
||||
[ "N", "", "", "", "", "", "5", "6", "7", "8"],
|
||||
[ "Y", "", "", "", "", "", "Z", "G", "", "_ESC"],
|
||||
[ "S", "", "", "", "", "", "C", "", "_UP", "" ],
|
||||
[ "", "", "", "", "", "", "", "_LT", "", "_RT"],
|
||||
[ "A", "", "T", "O", "", "", "_SP", "", "_DN", ""]
|
||||
]
|
|
@ -0,0 +1 @@
|
|||
../../../src/video/Basic.fsh
|
|
@ -0,0 +1 @@
|
|||
../../../src/video/Basic.vsh
|
|
@ -0,0 +1,19 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.3.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="apple2ix" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contains the path to the SDK. It should *NOT* be checked into
|
||||
Version Control Systems. -->
|
||||
<property file="local.properties" />
|
||||
|
||||
<!-- The ant.properties file can be created by you. It is only edited by the
|
||||
'android' tool to add properties to it.
|
||||
This is the place to change some Ant specific build properties.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
source.dir
|
||||
The name of the source directory. Default is 'src'.
|
||||
out.dir
|
||||
The name of the output directory. Default is 'bin'.
|
||||
|
||||
For other overridable properties, look at the beginning of the rules
|
||||
files in the SDK, at tools/ant/build.xml
|
||||
|
||||
Properties related to the SDK location or the project target should
|
||||
be updated using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="ant.properties" />
|
||||
|
||||
<!-- The project.properties file is created and updated by the 'android'
|
||||
tool, as well as ADT.
|
||||
|
||||
This contains project specific properties such as project target, and library
|
||||
dependencies. Lower level build properties are stored in ant.properties
|
||||
(or in .classpath for Eclipse projects).
|
||||
|
||||
This file is an integral part of the build system for your
|
||||
application and should be checked into Version Control Systems. -->
|
||||
<loadproperties srcFile="project.properties" />
|
||||
|
||||
<!-- quick check on sdk.dir -->
|
||||
<fail
|
||||
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
|
||||
unless="sdk.dir"
|
||||
/>
|
||||
|
||||
<!--
|
||||
Import per project custom build rules if present at the root of the project.
|
||||
This is the place to put custom intermediary targets such as:
|
||||
-pre-build
|
||||
-pre-compile
|
||||
-post-compile (This is typically used for code obfuscation.
|
||||
Compiled code location: ${out.classes.absolute.dir}
|
||||
If this is not done in place, override ${out.dex.input.absolute.dir})
|
||||
-post-package
|
||||
-post-build
|
||||
-pre-clean
|
||||
-->
|
||||
<import file="custom_rules.xml" optional="true" />
|
||||
|
||||
<!-- Import the actual build file.
|
||||
|
||||
To customize existing targets, there are two options:
|
||||
- Customize only one target:
|
||||
- copy/paste the target into this file, *before* the
|
||||
<import> task.
|
||||
- customize it to your needs.
|
||||
- Customize the whole content of build.xml
|
||||
- copy/paste the content of the rules files (minus the top node)
|
||||
into this file, replacing the <import> task.
|
||||
- customize to your needs.
|
||||
|
||||
***********************
|
||||
****** IMPORTANT ******
|
||||
***********************
|
||||
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
|
||||
in order to avoid having your file be overridden by tools such as "android update project"
|
||||
-->
|
||||
<!-- version-tag: 1 -->
|
||||
<import file="${sdk.dir}/tools/ant/build.xml" />
|
||||
|
||||
</project>
|
|
@ -0,0 +1,20 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
|
@ -0,0 +1,6 @@
|
|||
#Wed Apr 10 15:27:10 PDT 2013
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
|
|
@ -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 "$@"
|
|
@ -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,0 +1,8 @@
|
|||
APP_ABI := armeabi armeabi-v7a x86
|
||||
|
||||
# Do not change APP_PLATFORM if we care about Gingerbread (2.3.3) devices! We must compile against android-10,
|
||||
# otherwise we may encounter runtime load-library errors from symbols that should have been inlined against older
|
||||
# Bionic. e.g., getpagesize()
|
||||
APP_PLATFORM := android-10
|
||||
|
||||
APP_STL := gnustl_static
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
extern unsigned long android_deviceSampleRateHz;
|
||||
extern unsigned long android_monoBufferSubmitSizeSamples;
|
||||
extern unsigned long android_stereoBufferSubmitSizeSamples;
|
||||
|
||||
// architectures
|
||||
|
||||
extern bool android_armArch;
|
||||
extern bool android_armArchV7A;
|
||||
extern bool android_arm64Arch;
|
||||
|
||||
extern bool android_x86;
|
||||
extern bool android_x86_64;
|
||||
|
||||
// vector instructions availability
|
||||
|
||||
extern bool android_armNeonEnabled;
|
||||
extern bool android_x86SSSE3Enabled;
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "androidkeys.h"
|
||||
|
||||
// Codepaths for Bluetooth or other HID keyboards attached to an Android device.
|
||||
// For OpenGL emulated touchscreen keyboard, see gltouchkbd.c
|
||||
|
||||
static inline bool _is_shifted(int metaState) {
|
||||
return (metaState & META_SHIFT_MASK);
|
||||
}
|
||||
|
||||
static inline bool _is_ctrl(int metaState) {
|
||||
return (metaState & META_CTRL_LEFT_ON) || (metaState & META_CTRL_RIGHT_ON);
|
||||
}
|
||||
|
||||
void android_keycode_to_emulator(int keyCode, int metaState, bool pressed) {
|
||||
int key = -1;
|
||||
bool isASCII = true;
|
||||
|
||||
do {
|
||||
if ((keyCode >= KEYCODE_NUMPAD_0) && (keyCode <= KEYCODE_NUMPAD_9)) {
|
||||
key = keyCode - ASCII_NUMPAD_0_OFFSET;
|
||||
break;
|
||||
} else if ((keyCode >= KEYCODE_A) && (keyCode <= KEYCODE_Z)) {
|
||||
key = caps_lock || _is_shifted(metaState) ? (keyCode + ASCII_UCASE_OFFSET) : (keyCode + ASCII_LCASE_OFFSET);
|
||||
break;
|
||||
} else if ((keyCode >= KEYCODE_F1) && (keyCode <= KEYCODE_F12)) {
|
||||
isASCII = false;
|
||||
key = keyCode - FN_OFFSET;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KEYCODE_0:
|
||||
key = _is_shifted(metaState) ? ')' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_1:
|
||||
key = _is_shifted(metaState) ? '!' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_2:
|
||||
key = _is_shifted(metaState) ? '@' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_3:
|
||||
key = _is_shifted(metaState) ? '#' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_4:
|
||||
key = _is_shifted(metaState) ? '$' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_5:
|
||||
key = _is_shifted(metaState) ? '%' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_6:
|
||||
key = _is_shifted(metaState) ? '^' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_7:
|
||||
key = _is_shifted(metaState) ? '&' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_8:
|
||||
key = _is_shifted(metaState) ? '*' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_9:
|
||||
key = _is_shifted(metaState) ? '(' : keyCode + ASCII_0_OFFSET;
|
||||
break;
|
||||
case KEYCODE_APOSTROPHE:
|
||||
key = _is_shifted(metaState) ? '"' : '\'';
|
||||
break;
|
||||
case KEYCODE_AT:
|
||||
key = '@';
|
||||
break;
|
||||
case KEYCODE_BACKSLASH:
|
||||
key = _is_shifted(metaState) ? '|' : '\\';
|
||||
break;
|
||||
case KEYCODE_COMMA:
|
||||
key = _is_shifted(metaState) ? '<' : ',';
|
||||
break;
|
||||
case KEYCODE_EQUALS:
|
||||
key = _is_shifted(metaState) ? '+' : '=';
|
||||
break;
|
||||
case KEYCODE_GRAVE:
|
||||
key = _is_shifted(metaState) ? '~' : '`';
|
||||
break;
|
||||
case KEYCODE_LEFT_BRACKET:
|
||||
key = _is_shifted(metaState) ? '{' : '[';
|
||||
break;
|
||||
case KEYCODE_MINUS:
|
||||
key = _is_shifted(metaState) ? '_' : '-';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_ADD:
|
||||
key = '+';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_COMMA:
|
||||
key = ',';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_DIVIDE:
|
||||
key = '/';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_DOT:
|
||||
key = '.';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_EQUALS:
|
||||
key = '=';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_LEFT_PAREN:
|
||||
key = '(';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_RIGHT_PAREN:
|
||||
key = ')';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_MULTIPLY:
|
||||
key = '*';
|
||||
break;
|
||||
case KEYCODE_NUMPAD_SUBTRACT:
|
||||
key = '-';
|
||||
break;
|
||||
case KEYCODE_PERIOD:
|
||||
key = _is_shifted(metaState) ? '>' : '.';
|
||||
break;
|
||||
case KEYCODE_PLUS:
|
||||
key = '+';
|
||||
break;
|
||||
case KEYCODE_POUND:
|
||||
key = '#';
|
||||
break;
|
||||
case KEYCODE_RIGHT_BRACKET:
|
||||
key = _is_shifted(metaState) ? '}' : ']';
|
||||
break;
|
||||
case KEYCODE_SEMICOLON:
|
||||
key = _is_shifted(metaState) ? ':' : ';';
|
||||
break;
|
||||
case KEYCODE_SLASH:
|
||||
key = _is_shifted(metaState) ? '?' : '/';
|
||||
break;
|
||||
case KEYCODE_SPACE:
|
||||
key = ' ';
|
||||
break;
|
||||
case KEYCODE_STAR:
|
||||
key = '*';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (key != -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// META
|
||||
isASCII = false;
|
||||
|
||||
switch (keyCode) {
|
||||
case KEYCODE_DPAD_LEFT:
|
||||
key = SCODE_L;
|
||||
break;
|
||||
case KEYCODE_DPAD_RIGHT:
|
||||
key = SCODE_R;
|
||||
break;
|
||||
case KEYCODE_DPAD_DOWN:
|
||||
key = SCODE_D;
|
||||
break;
|
||||
case KEYCODE_DPAD_UP:
|
||||
key = SCODE_U;
|
||||
break;
|
||||
case KEYCODE_DPAD_CENTER:
|
||||
key = SCODE_HOME;
|
||||
break;
|
||||
|
||||
case KEYCODE_PAGE_UP:
|
||||
key = SCODE_PGUP;
|
||||
break;
|
||||
case KEYCODE_PAGE_DOWN:
|
||||
key = SCODE_PGDN;
|
||||
break;
|
||||
case KEYCODE_INSERT:
|
||||
key = SCODE_INS;
|
||||
break;
|
||||
|
||||
case KEYCODE_SHIFT_LEFT:
|
||||
key = SCODE_L_SHIFT;
|
||||
break;
|
||||
case KEYCODE_SHIFT_RIGHT:
|
||||
key = SCODE_R_SHIFT;
|
||||
break;
|
||||
|
||||
case KEYCODE_CTRL_LEFT:
|
||||
key = SCODE_L_CTRL;
|
||||
break;
|
||||
case KEYCODE_CTRL_RIGHT:
|
||||
key = SCODE_R_CTRL;
|
||||
break;
|
||||
|
||||
case KEYCODE_ALT_LEFT:
|
||||
case KEYCODE_META_LEFT:
|
||||
key = SCODE_L_ALT;
|
||||
break;
|
||||
case KEYCODE_ALT_RIGHT:
|
||||
case KEYCODE_META_RIGHT:
|
||||
key = SCODE_R_ALT;
|
||||
break;
|
||||
case KEYCODE_BREAKPAUSE:
|
||||
key = SCODE_BRK;
|
||||
break;
|
||||
case KEYCODE_CAPS_LOCK:
|
||||
caps_lock = (metaState & META_CAPS_LOCK_ON);
|
||||
return;
|
||||
case KEYCODE_DEL:
|
||||
key = SCODE_L;// DEL is prolly not what they meant =P
|
||||
break;
|
||||
case KEYCODE_ENTER:
|
||||
key = SCODE_RET;
|
||||
break;
|
||||
case KEYCODE_TAB:
|
||||
key = SCODE_TAB;
|
||||
break;
|
||||
case KEYCODE_ESC:
|
||||
key = SCODE_ESC;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} while (0);
|
||||
|
||||
LOG("keyCode:%08x -> key:%02x ('%c') metaState:%08x", keyCode, key, key, metaState);
|
||||
|
||||
if (isASCII && _is_ctrl(metaState)) {
|
||||
key = c_keys_ascii_to_scancode(key);
|
||||
c_keys_handle_input(key, true, false);
|
||||
isASCII = false;
|
||||
pressed = false;
|
||||
}
|
||||
|
||||
assert(key < 0x80);
|
||||
c_keys_handle_input(key, pressed, isASCII);
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
#define ASCII_UCASE_OFFSET 0x24
|
||||
#define ASCII_LCASE_OFFSET 0x44
|
||||
#define ASCII_0_OFFSET 0x29
|
||||
#define ASCII_NUMPAD_0_OFFSET 0x60
|
||||
#define FN_OFFSET 0x48
|
||||
|
||||
#define KEYCODE_0 0x07
|
||||
#define KEYCODE_1 0x08
|
||||
#define KEYCODE_2 0x09
|
||||
#define KEYCODE_3 0x0a
|
||||
#define KEYCODE_4 0x0b
|
||||
#define KEYCODE_5 0x0c
|
||||
#define KEYCODE_6 0x0d
|
||||
#define KEYCODE_7 0x0e
|
||||
#define KEYCODE_8 0x0f
|
||||
#define KEYCODE_9 0x10
|
||||
#define KEYCODE_A 0x1d
|
||||
#define KEYCODE_Z 0x36
|
||||
|
||||
#define KEYCODE_APOSTROPHE 0x4b
|
||||
#define KEYCODE_AT 0x4d
|
||||
#define KEYCODE_BACKSLASH 0x49
|
||||
#define KEYCODE_COMMA 0x37
|
||||
#define KEYCODE_EQUALS 0x46
|
||||
#define KEYCODE_GRAVE 0x44
|
||||
#define KEYCODE_LEFT_BRACKET 0x47
|
||||
#define KEYCODE_MINUS 0x45
|
||||
#define KEYCODE_PERIOD 0x38
|
||||
#define KEYCODE_PLUS 0x51
|
||||
#define KEYCODE_POUND 0x12
|
||||
#define KEYCODE_RIGHT_BRACKET 0x48
|
||||
#define KEYCODE_SEMICOLON 0x4a
|
||||
#define KEYCODE_SLASH 0x4c
|
||||
#define KEYCODE_SPACE 0x3e
|
||||
#define KEYCODE_STAR 0x11
|
||||
|
||||
// numpad
|
||||
#define KEYCODE_NUMPAD_0 0x90
|
||||
#define KEYCODE_NUMPAD_9 0x99
|
||||
#define KEYCODE_NUMPAD_ADD 0x9d
|
||||
#define KEYCODE_NUMPAD_COMMA 0x9f
|
||||
#define KEYCODE_NUMPAD_DIVIDE 0x9a
|
||||
#define KEYCODE_NUMPAD_DOT 0x9e
|
||||
#define KEYCODE_NUMPAD_EQUALS 0xa1
|
||||
#define KEYCODE_NUMPAD_LEFT_PAREN 0xa2
|
||||
#define KEYCODE_NUMPAD_RIGHT_PAREN 0xa3
|
||||
#define KEYCODE_NUMPAD_MULTIPLY 0x9b
|
||||
#define KEYCODE_NUMPAD_SUBTRACT 0x9c
|
||||
|
||||
// various meta keys
|
||||
#define KEYCODE_ALT_LEFT 0x39
|
||||
#define KEYCODE_ALT_RIGHT 0x3a
|
||||
#define KEYCODE_BREAKPAUSE 0x79
|
||||
#define KEYCODE_CAPS_LOCK 0x73
|
||||
#define KEYCODE_CTRL_LEFT 0x71
|
||||
#define KEYCODE_CTRL_RIGHT 0x72
|
||||
#define KEYCODE_DEL 0x43
|
||||
#define KEYCODE_ENTER 0x42
|
||||
#define KEYCODE_ESC 0x6f
|
||||
#define KEYCODE_INSERT 0x7c
|
||||
#define KEYCODE_META_LEFT 0x75
|
||||
#define KEYCODE_META_RIGHT 0x76
|
||||
#define KEYCODE_PAGE_DOWN 0x5d
|
||||
#define KEYCODE_PAGE_UP 0x5c
|
||||
#define KEYCODE_SHIFT_LEFT 0x3b
|
||||
#define KEYCODE_SHIFT_RIGHT 0x3c
|
||||
#define KEYCODE_TAB 0x3d
|
||||
|
||||
// F1-F12
|
||||
#define KEYCODE_F1 0x83
|
||||
#define KEYCODE_F12 0x8e
|
||||
|
||||
// Directional pad movements
|
||||
#define KEYCODE_DPAD_CENTER 0x17
|
||||
#define KEYCODE_DPAD_DOWN 0x14
|
||||
#define KEYCODE_DPAD_LEFT 0x15
|
||||
#define KEYCODE_DPAD_RIGHT 0x16
|
||||
#define KEYCODE_DPAD_UP 0x13
|
||||
|
||||
// Game pad buttons
|
||||
#define KEYCODE_BUTTON_1 0xbc
|
||||
#define KEYCODE_BUTTON_16 0xcb
|
||||
#define KEYCODE_BUTTON_A 0x60
|
||||
#define KEYCODE_BUTTON_B 0x61
|
||||
#define KEYCODE_BUTTON_C 0x62
|
||||
#define KEYCODE_BUTTON_L1 0x66
|
||||
#define KEYCODE_BUTTON_L2 0x68
|
||||
#define KEYCODE_BUTTON_R1 0x67
|
||||
#define KEYCODE_BUTTON_R2 0x69
|
||||
|
||||
#define META_ALT_LEFT_ON 0x00000010
|
||||
#define META_ALT_RIGHT_ON 0x00000020
|
||||
#define META_CAPS_LOCK_ON 0x00100000
|
||||
#define META_CTRL_LEFT_ON 0x00002000
|
||||
#define META_CTRL_RIGHT_ON 0x00004000
|
||||
#define META_SHIFT_MASK 0x000000c1
|
||||
#define META_SHIFT_ON 0x00000040
|
||||
#define META_SHIFT_LEFT_ON 0x00000040
|
||||
#define META_SHIFT_RIGHT_ON 0x00000080
|
||||
|
||||
void android_keycode_to_emulator(int keyCode, int metaState, bool pressed);
|
||||
|
|
@ -0,0 +1 @@
|
|||
build.sh
|
|
@ -0,0 +1 @@
|
|||
../../src
|
|
@ -0,0 +1,61 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
|
||||
PACKAGE_NAME := "apple2ix"
|
||||
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
|
||||
include $(COMMON_SOURCES_MK)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Breakpad crash reporter ...
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := breakpad_client
|
||||
|
||||
# hmmm, Breakpad's README.ANDROID seems to suggest you shouldn't need to do this kludgery ...
|
||||
BREAKPAD_SRC_PATH := $(APPLE2_SRC_PATH)/../externals/breakpad/src
|
||||
BREAKPAD_CFLAGS := -I$(BREAKPAD_SRC_PATH) -I$(APPLE2_SRC_PATH)/common/android/include
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Android build config
|
||||
|
||||
LOCAL_CPP_EXTENSION := .C
|
||||
LOCAL_CPPFLAGS := -std=gnu++11
|
||||
|
||||
LOCAL_MODULE := libapple2ix
|
||||
LOCAL_SRC_FILES := jnicrash.c $(APPLE2_SRC_PATH)/breakpad.C
|
||||
#LOCAL_ARM_MODE := arm
|
||||
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) $(BREAKPAD_CFLAGS)
|
||||
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
|
||||
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
|
||||
LOCAL_CFLAGS += -DNO_UNDERSCORES=1
|
||||
else
|
||||
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
|
||||
endif
|
||||
|
||||
ifeq ($(EMBEDDED_STACKWALKER),1)
|
||||
LOCAL_CPPFLAGS += -DEMBEDDED_STACKWALKER=1
|
||||
else
|
||||
ifeq ($(RUNNING_GDB),1)
|
||||
# nothing to do
|
||||
else
|
||||
$(error OOPS, for now you should build with EMBEDDED_STACKWALKER=1)
|
||||
endif
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
|
||||
|
||||
# Build a shared library and let Java/Dalvik drive
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
# --OR-- Build an executable so native can drive this show
|
||||
#include $(BUILD_EXECUTABLE)
|
||||
|
||||
ifeq ($(RUNNING_GDB),1)
|
||||
# nothing to do
|
||||
else
|
||||
$(call import-module, breakpad/android/google_breakpad)
|
||||
endif
|
||||
$(call import-module, android/cpufeatures)
|
|
@ -0,0 +1 @@
|
|||
../../externals/breakpad
|
|
@ -0,0 +1 @@
|
|||
breakpad/android/google_breakpad/Android.mk
|
|
@ -0,0 +1,182 @@
|
|||
#!/bin/sh
|
||||
|
||||
package_id="org.deadc0de.apple2ix.basic"
|
||||
apple2_src_path=apple2ix-src
|
||||
glue_srcs="$apple2_src_path/disk.c $apple2_src_path/misc.c $apple2_src_path/display.c $apple2_src_path/vm.c $apple2_src_path/cpu-supp.c $apple2_src_path/audio/speaker.c $apple2_src_path/audio/mockingboard.c"
|
||||
|
||||
usage() {
|
||||
if test "$(basename $0)" = "clean" ; then
|
||||
echo "$0"
|
||||
echo " # cleans NDK build of $package_id"
|
||||
elif test "$(basename $0)" = "uninstall" ; then
|
||||
echo "$0"
|
||||
echo " # uninstalls $package_id"
|
||||
else
|
||||
echo "$0 [build] [load|debug]"
|
||||
echo " # default builds $package_id and then load or debug"
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
export EMBEDDED_STACKWALKER=1
|
||||
|
||||
while test "x$1" != "x"; do
|
||||
case "$1" in
|
||||
"debug")
|
||||
do_debug=1
|
||||
;;
|
||||
|
||||
"load")
|
||||
do_load=1
|
||||
;;
|
||||
|
||||
"-h")
|
||||
usage
|
||||
;;
|
||||
|
||||
"--help")
|
||||
usage
|
||||
;;
|
||||
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
set -x
|
||||
|
||||
if test "$(basename $0)" = "clean" ; then
|
||||
|
||||
/bin/rm $apple2_src_path/rom.c
|
||||
/bin/rm $apple2_src_path/font.c
|
||||
/bin/rm $apple2_src_path/x86/glue.S
|
||||
/bin/rm $apple2_src_path/arm/glue.S
|
||||
|
||||
# considered dangerous
|
||||
/bin/rm -rf ../bin
|
||||
/bin/rm -rf ../libs
|
||||
/bin/rm -rf ../gen
|
||||
/bin/rm -rf ../obj
|
||||
|
||||
ndk-build V=1 NDK_MODULE_PATH=. clean
|
||||
##cd ..
|
||||
##ant clean
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
/bin/rm Android.mk
|
||||
|
||||
if test "$(basename $0)" = "uninstall" ; then
|
||||
adb uninstall $package_id
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#CC=`which clang`
|
||||
CC=`which gcc`
|
||||
CFLAGS="-std=gnu11"
|
||||
|
||||
# ROMz
|
||||
$CC $CFLAGS -o $apple2_src_path/genrom $apple2_src_path/genrom.c && \
|
||||
$apple2_src_path/genrom $apple2_src_path/rom/apple_IIe.rom $apple2_src_path/rom/slot6.rom > $apple2_src_path/rom.c
|
||||
|
||||
# font
|
||||
$CC $CFLAGS -o $apple2_src_path/genfont $apple2_src_path/genfont.c && \
|
||||
$apple2_src_path/genfont < $apple2_src_path/font.txt > $apple2_src_path/font.c
|
||||
|
||||
# glue
|
||||
$apple2_src_path/x86/genglue $glue_srcs > $apple2_src_path/x86/glue.S
|
||||
$apple2_src_path/arm/genglue $glue_srcs > $apple2_src_path/arm/glue.S
|
||||
|
||||
if test "$(basename $0)" = "testcpu" ; then
|
||||
ln -s testcpu.mk Android.mk
|
||||
elif test "$(basename $0)" = "testvm" ; then
|
||||
ln -s testvm.mk Android.mk
|
||||
elif test "$(basename $0)" = "testdisplay" ; then
|
||||
ln -s testdisplay.mk Android.mk
|
||||
elif test "$(basename $0)" = "testdisk" ; then
|
||||
ln -s testdisk.mk Android.mk
|
||||
else
|
||||
ln -s apple2ix.mk Android.mk
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# build native sources
|
||||
ndk-build V=1 NDK_MODULE_PATH=. NDK_DEBUG=1 # NDK_TOOLCHAIN_VERSION=clang
|
||||
ret=$?
|
||||
if test "x$ret" != "x0" ; then
|
||||
exit $ret
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# Symbolicate and move symbols file into location to be deployed on device
|
||||
|
||||
SYMFILE=libapple2ix.so.sym
|
||||
ARCHES_TO_SYMBOLICATE='armeabi armeabi-v7a x86'
|
||||
|
||||
for arch in $ARCHES_TO_SYMBOLICATE ; do
|
||||
SYMDIR=../assets/symbols/$arch/libapple2ix.so
|
||||
|
||||
# remove old symbols (if any)
|
||||
/bin/rm -rf $SYMDIR
|
||||
|
||||
# Run Breakpad's dump_syms
|
||||
../../externals/bin/dump_syms ../obj/local/$arch/libapple2ix.so > $SYMFILE
|
||||
|
||||
ret=$?
|
||||
if test "x$ret" != "x0" ; then
|
||||
echo "OOPS, dump_syms failed for $arch"
|
||||
exit $ret
|
||||
fi
|
||||
|
||||
# strip to the just the numeric id in the .sym header and verify it makes sense
|
||||
sym_id=$(head -1 $SYMFILE | cut -d ' ' -f 4)
|
||||
sym_id_check=$(echo $sym_id | wc -c)
|
||||
if test "x$sym_id_check" != "x34" ; then
|
||||
echo "OOPS symbol header not expected size, meat-space intervention needed =P"
|
||||
exit 1
|
||||
fi
|
||||
sym_id_check=$(echo $sym_id | tr -d 'A-Fa-f0-9' | wc -c)
|
||||
if test "x$sym_id_check" != "x1" ; then
|
||||
echo "OOPS unexpected characters in symbol header, meat-space intervention needed =P"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p $SYMDIR/$sym_id
|
||||
ret=$?
|
||||
if test "x$ret" != "x0" ; then
|
||||
echo "OOPS, could not create symbols directory for arch:$arch and sym_id:$sym_id"
|
||||
exit $ret
|
||||
fi
|
||||
|
||||
/bin/mv $SYMFILE $SYMDIR/$sym_id/
|
||||
ret=$?
|
||||
if test "x$ret" != "x0" ; then
|
||||
echo "OOPS, could not move $SYMFILE to $SYMDIR/$sym_id/"
|
||||
exit $ret
|
||||
fi
|
||||
done
|
||||
|
||||
###############################################################################
|
||||
# usually we should build the Java stuff from within Android Studio
|
||||
if test "x$do_load" = "x1" ; then
|
||||
ant -f ../build.xml debug install
|
||||
ret=$?
|
||||
if test "x$ret" != "x0" ; then
|
||||
exit $ret
|
||||
fi
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
if test "x$do_debug" = "x1" ; then
|
||||
export RUNNING_GDB=1
|
||||
cd ..
|
||||
ndk-gdb --nowait --force --verbose --launch=org.deadc0de.apple2ix.Apple2Activity
|
||||
elif test "x$do_load" = "x1" ; then
|
||||
adb shell am start -a android.intent.action.MAIN -n org.deadc0de.apple2ix.basic/org.deadc0de.apple2ix.Apple2Activity
|
||||
fi
|
||||
|
||||
set +x
|
||||
|
|
@ -0,0 +1 @@
|
|||
build.sh
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
// Keep these in sync with the Java side
|
||||
enum {
|
||||
CRASH_JAVA=0,
|
||||
CRASH_NULL_DEREF,
|
||||
CRASH_STACKCALL_OVERFLOW,
|
||||
CRASH_STACKBUF_OVERFLOW,
|
||||
// MOAR!
|
||||
};
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
static volatile int __attribute__((noinline)) _crash_null_deref(void) {
|
||||
static volatile uintptr_t *ptr = NULL;
|
||||
while ((ptr+1)) {
|
||||
*ptr++ = 0xAA;
|
||||
}
|
||||
return (int)ptr[0];
|
||||
}
|
||||
|
||||
static volatile int (*funPtr0)(void) = NULL;
|
||||
static volatile int __attribute__((noinline)) _crash_stackcall_overflow(void) {
|
||||
if (funPtr0) {
|
||||
funPtr0();
|
||||
funPtr0 = NULL;
|
||||
} else {
|
||||
funPtr0 = &_crash_stackcall_overflow;
|
||||
funPtr0();
|
||||
}
|
||||
return getpid();
|
||||
}
|
||||
|
||||
static volatile int (*funPtr1)(unsigned int) = NULL;
|
||||
static volatile int __attribute__((noinline)) _crash_stackbuf_overflow0(unsigned int smashSize) {
|
||||
volatile char buf[32];
|
||||
memset((char *)buf, 0x55, smashSize);
|
||||
return (int)&buf[0];
|
||||
}
|
||||
|
||||
static volatile int __attribute__((noinline)) _crash_stackbuf_overflow(void) {
|
||||
static volatile unsigned int smashSize = 0;
|
||||
while (1) {
|
||||
if (funPtr1) {
|
||||
funPtr1(smashSize);
|
||||
funPtr1 = NULL;
|
||||
} else {
|
||||
funPtr1 = &_crash_stackbuf_overflow0;
|
||||
funPtr1(smashSize);
|
||||
}
|
||||
|
||||
smashSize += 32;
|
||||
if (!smashSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return getpid();
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativePerformCrash(JNIEnv *env, jclass cls, jint crashType) {
|
||||
#warning FIXME TODO ... we should turn off test codepaths in release build =D
|
||||
LOG("... performing crash of type : %d", crashType);
|
||||
|
||||
switch (crashType) {
|
||||
case CRASH_NULL_DEREF:
|
||||
_crash_null_deref();
|
||||
break;
|
||||
|
||||
case CRASH_STACKCALL_OVERFLOW:
|
||||
_crash_stackcall_overflow();
|
||||
break;
|
||||
|
||||
case CRASH_STACKBUF_OVERFLOW:
|
||||
_crash_stackbuf_overflow();
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown crasher, just abort ...
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2CrashHandler_nativeProcessCrash(JNIEnv *env, jclass cls, jstring jCrashPath, jstring jOutputPath) {
|
||||
if (!(crashHandler && crashHandler->processCrash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("...");
|
||||
|
||||
const char *crashPath = (*env)->GetStringUTFChars(env, jCrashPath, NULL);
|
||||
const char *outputPath = (*env)->GetStringUTFChars(env, jOutputPath, NULL);
|
||||
FILE *outputFILE = NULL;
|
||||
char *symbolsPath = NULL;
|
||||
|
||||
do {
|
||||
outputFILE = TEMP_FAILURE_RETRY_FOPEN(fopen(outputPath, "w"));
|
||||
if (!outputFILE) {
|
||||
ERRLOG("could not open %s", outputPath);
|
||||
break;
|
||||
}
|
||||
|
||||
if (android_armArchV7A) {
|
||||
asprintf(&symbolsPath, "%s/symbols/armeabi-v7a", data_dir);
|
||||
} else /*if (android_armArch)*/ {
|
||||
asprintf(&symbolsPath, "%s/symbols/armeabi", data_dir);
|
||||
} /*else { moar archs ... } */
|
||||
|
||||
bool success = crashHandler->processCrash(crashPath, symbolsPath, outputFILE);
|
||||
if (!success) {
|
||||
RELEASE_LOG("CRASH REPORT PROCESSING FAILED ...");
|
||||
}
|
||||
} while (0);
|
||||
|
||||
if (outputFILE) {
|
||||
TEMP_FAILURE_RETRY(fflush(outputFILE));
|
||||
TEMP_FAILURE_RETRY(fclose(outputFILE));
|
||||
}
|
||||
|
||||
if (symbolsPath) {
|
||||
FREE(symbolsPath);
|
||||
}
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jCrashPath, crashPath);
|
||||
(*env)->ReleaseStringUTFChars(env, jOutputPath, outputPath);
|
||||
}
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "androidkeys.h"
|
||||
|
||||
#include <cpu-features.h>
|
||||
#include <jni.h>
|
||||
|
||||
unsigned long android_deviceSampleRateHz = 0;
|
||||
unsigned long android_monoBufferSubmitSizeSamples = 0;
|
||||
unsigned long android_stereoBufferSubmitSizeSamples = 0;
|
||||
|
||||
bool android_armArch = false;
|
||||
bool android_armArchV7A = false;
|
||||
bool android_arm64Arch = false;
|
||||
|
||||
bool android_x86 = false;
|
||||
bool android_x86_64 = false;
|
||||
|
||||
bool android_armNeonEnabled = false;
|
||||
bool android_x86SSSE3Enabled = false;
|
||||
|
||||
enum {
|
||||
ANDROID_ACTION_DOWN = 0x0,
|
||||
ANDROID_ACTION_UP = 0x1,
|
||||
ANDROID_ACTION_MOVE = 0x2,
|
||||
ANDROID_ACTION_CANCEL = 0x3,
|
||||
ANDROID_ACTION_POINTER_DOWN = 0x5,
|
||||
ANDROID_ACTION_POINTER_UP = 0x6,
|
||||
};
|
||||
|
||||
typedef enum lifecycle_seq_t {
|
||||
APP_RUNNING = 0,
|
||||
APP_REQUESTED_SHUTDOWN,
|
||||
APP_FINISHED,
|
||||
} lifecycle_seq_t;
|
||||
|
||||
static lifecycle_seq_t appState = APP_RUNNING;
|
||||
|
||||
#if TESTING
|
||||
static void _run_tests(void) {
|
||||
char *local_argv[] = {
|
||||
"-f",
|
||||
NULL
|
||||
};
|
||||
int local_argc = 0;
|
||||
for (char **p = &local_argv[0]; *p != NULL; p++) {
|
||||
++local_argc;
|
||||
}
|
||||
#if defined(TEST_CPU)
|
||||
// Currently this test is the only one that runs as a black screen
|
||||
extern int test_cpu(int, char *[]);
|
||||
test_cpu(local_argc, local_argv);
|
||||
tkill(getpid(), SIGKILL); // and we're done ...
|
||||
#elif defined(TEST_VM)
|
||||
extern int test_vm(int, char *[]);
|
||||
test_vm(local_argc, local_argv);
|
||||
#elif defined(TEST_DISPLAY)
|
||||
extern int test_display(int, char *[]);
|
||||
test_display(local_argc, local_argv);
|
||||
#elif defined(TEST_DISK)
|
||||
extern int test_disk(int, char *[]);
|
||||
test_disk(local_argc, local_argv);
|
||||
#else
|
||||
# error "OOPS, no tests specified"
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int _androidTouchEvent2InterfaceEvent(jint action) {
|
||||
switch (action) {
|
||||
case ANDROID_ACTION_DOWN:
|
||||
return TOUCH_DOWN;
|
||||
case ANDROID_ACTION_MOVE:
|
||||
return TOUCH_MOVE;
|
||||
case ANDROID_ACTION_UP:
|
||||
return TOUCH_UP;
|
||||
case ANDROID_ACTION_POINTER_DOWN:
|
||||
return TOUCH_POINTER_DOWN;
|
||||
case ANDROID_ACTION_POINTER_UP:
|
||||
return TOUCH_POINTER_UP;
|
||||
case ANDROID_ACTION_CANCEL:
|
||||
return TOUCH_CANCEL;
|
||||
default:
|
||||
LOG("Unknown Android event : %d", action);
|
||||
return TOUCH_CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
static void discover_cpu_family(void) {
|
||||
LOG("Discovering CPU family...");
|
||||
|
||||
AndroidCpuFamily family = android_getCpuFamily();
|
||||
uint64_t features = android_getCpuFeatures();
|
||||
if (family == ANDROID_CPU_FAMILY_X86) {
|
||||
if (features & ANDROID_CPU_X86_FEATURE_SSSE3) {
|
||||
LOG("nANDROID_CPU_X86_FEATURE_SSSE3");
|
||||
android_x86SSSE3Enabled = true;
|
||||
}
|
||||
if (features & ANDROID_CPU_X86_FEATURE_MOVBE) {
|
||||
LOG("ANDROID_CPU_X86_FEATURE_MOVBE");
|
||||
}
|
||||
if (features & ANDROID_CPU_X86_FEATURE_POPCNT) {
|
||||
LOG("ANDROID_CPU_X86_FEATURE_POPCNT");
|
||||
}
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM) {
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_ARMv7");
|
||||
android_armArchV7A = true;
|
||||
} else {
|
||||
LOG("!!! NOT ANDROID_CPU_ARM_FEATURE_ARMv7");
|
||||
android_armArch = true;
|
||||
}
|
||||
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_VFPv3) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_VFPv3");
|
||||
}
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_NEON");
|
||||
android_armNeonEnabled = true;
|
||||
}
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_LDREX_STREX) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_LDREX_STREX");
|
||||
}
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM64) {
|
||||
#warning FIXME TODO ...
|
||||
//android_arm64Arch = true;
|
||||
android_armArchV7A = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// JNI functions
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobject obj, jstring j_dataDir, jint sampleRate, jint monoBufferSize, jint stereoBufferSize) {
|
||||
const char *dataDir = (*env)->GetStringUTFChars(env, j_dataDir, 0);
|
||||
|
||||
// Android lifecycle can call onCreate() multiple times...
|
||||
if (data_dir) {
|
||||
LOG("IGNORING multiple calls to nativeOnCreate ...");
|
||||
return;
|
||||
}
|
||||
|
||||
discover_cpu_family();
|
||||
|
||||
// Do not remove this deadc0de ... it forces a runtime load-library/link error on Gingerbread devices if we have
|
||||
// incorrectly compiled the app against a later version of the NDK!!!
|
||||
int pagesize = getpagesize();
|
||||
LOG("PAGESIZE IS : %d", pagesize);
|
||||
|
||||
data_dir = strdup(dataDir);
|
||||
if (crashHandler && crashHandler->init) {
|
||||
crashHandler->init(data_dir);
|
||||
}
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, j_dataDir, dataDir);
|
||||
LOG("data_dir : %s", data_dir);
|
||||
|
||||
android_deviceSampleRateHz = (unsigned long)sampleRate;
|
||||
android_monoBufferSubmitSizeSamples = (unsigned long)monoBufferSize;
|
||||
android_stereoBufferSubmitSizeSamples = (unsigned long)stereoBufferSize;
|
||||
|
||||
#if TESTING
|
||||
assert(cpu_thread_id == 0 && "CPU thread must not be initialized yet...");
|
||||
_run_tests();
|
||||
// CPU thread is started from testsuite (if needed)
|
||||
#else
|
||||
cpu_pause();
|
||||
emulator_start();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsChanged(JNIEnv *env, jclass cls, jint width, jint height) {
|
||||
// WARNING : this can happen on non-GL thread
|
||||
LOG("...");
|
||||
video_backend->reshape(width, height);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2View_nativeGraphicsInitialized(JNIEnv *env, jclass cls, jint width, jint height) {
|
||||
// WANRING : this needs to happen on the GL thread only
|
||||
LOG("width:%d height:%d", width, height);
|
||||
video_shutdown();
|
||||
video_backend->reshape(width, height);
|
||||
video_backend->init((void *)0);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationResume(JNIEnv *env, jobject obj) {
|
||||
if (!cpu_isPaused()) {
|
||||
return;
|
||||
}
|
||||
LOG("...");
|
||||
#if TESTING
|
||||
// test driver thread is managing CPU
|
||||
#else
|
||||
cpu_resume();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEmulationPause(JNIEnv *env, jobject obj) {
|
||||
if (appState != APP_RUNNING) {
|
||||
return;
|
||||
}
|
||||
if (cpu_isPaused()) {
|
||||
return;
|
||||
}
|
||||
LOG("...");
|
||||
|
||||
#if TESTING
|
||||
// test driver thread is managing CPU
|
||||
#else
|
||||
cpu_pause();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2View_nativeRender(JNIEnv *env, jclass cls) {
|
||||
SCOPE_TRACE_VIDEO("nativeRender");
|
||||
|
||||
if (UNLIKELY(appState != APP_RUNNING)) {
|
||||
if (appState == APP_REQUESTED_SHUTDOWN) {
|
||||
appState = APP_FINISHED;
|
||||
emulator_shutdown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#if FPS_LOG
|
||||
static uint32_t prevCount = 0;
|
||||
static uint32_t idleCount = 0;
|
||||
|
||||
idleCount++;
|
||||
|
||||
static struct timespec prev = { 0 };
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
|
||||
if (now.tv_sec != prev.tv_sec) {
|
||||
LOG("FPS : %u", idleCount-prevCount);
|
||||
prevCount = idleCount;
|
||||
prev = now;
|
||||
}
|
||||
#endif
|
||||
|
||||
video_backend->render();
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeReboot(JNIEnv *env, jobject obj) {
|
||||
LOG("...");
|
||||
cpu65_reboot();
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnQuit(JNIEnv *env, jobject obj) {
|
||||
#if TESTING
|
||||
// test driver thread is managing CPU
|
||||
#else
|
||||
appState = APP_REQUESTED_SHUTDOWN;
|
||||
|
||||
LOG("...");
|
||||
|
||||
disk6_eject(0);
|
||||
disk6_eject(1);
|
||||
|
||||
cpu_resume();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyDown(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
|
||||
if (UNLIKELY(appState != APP_RUNNING)) {
|
||||
return;
|
||||
}
|
||||
android_keycode_to_emulator(keyCode, metaState, true);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnKeyUp(JNIEnv *env, jobject obj, jint keyCode, jint metaState) {
|
||||
if (UNLIKELY(appState != APP_RUNNING)) {
|
||||
return;
|
||||
}
|
||||
android_keycode_to_emulator(keyCode, metaState, false);
|
||||
}
|
||||
|
||||
jlong Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnTouch(JNIEnv *env, jobject obj, jint action, jint pointerCount, jint pointerIndex, jfloatArray xCoords, jfloatArray yCoords) {
|
||||
//LOG(": %d/%d/%d :", action, pointerCount, pointerIndex);
|
||||
|
||||
SCOPE_TRACE_TOUCH("nativeOnTouch");
|
||||
|
||||
if (UNLIKELY(appState != APP_RUNNING)) {
|
||||
return 0x0LL;
|
||||
}
|
||||
|
||||
jfloat *x_coords = (*env)->GetFloatArrayElements(env, xCoords, 0);
|
||||
jfloat *y_coords = (*env)->GetFloatArrayElements(env, yCoords, 0);
|
||||
|
||||
int joyaction = _androidTouchEvent2InterfaceEvent(action);
|
||||
|
||||
//for (unsigned int i=0; i<pointerCount; i++) {
|
||||
// LOG("\t[%f,%f]", x_coords[i], y_coords[i]);
|
||||
//}
|
||||
|
||||
int64_t flags = interface_onTouchEvent(joyaction, pointerCount, pointerIndex, x_coords, y_coords);
|
||||
|
||||
(*env)->ReleaseFloatArrayElements(env, xCoords, x_coords, 0);
|
||||
(*env)->ReleaseFloatArrayElements(env, yCoords, y_coords, 0);
|
||||
return flags;
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeChooseDisk(JNIEnv *env, jobject obj, jstring jPath, jboolean driveA, jboolean readOnly) {
|
||||
const char *path = (*env)->GetStringUTFChars(env, jPath, NULL);
|
||||
int drive = driveA ? 0 : 1;
|
||||
int ro = readOnly ? 1 : 0;
|
||||
|
||||
assert(cpu_isPaused() && "considered dangerous to insert disk image when CPU thread is running");
|
||||
|
||||
LOG(": (%s, %s, %s)", path, driveA ? "drive A" : "drive B", readOnly ? "read only" : "read/write");
|
||||
if (disk6_insert(drive, path, ro)) {
|
||||
char *gzPath = NULL;
|
||||
asprintf(&gzPath, "%s.gz", path);
|
||||
if (disk6_insert(drive, gzPath, ro)) {
|
||||
char *diskImageUnreadable = "Disk Image Unreadable";
|
||||
unsigned int cols = strlen(diskImageUnreadable);
|
||||
video_backend->animation_showMessage(diskImageUnreadable, cols, 1);
|
||||
} else {
|
||||
video_backend->animation_showDiskChosen(drive);
|
||||
}
|
||||
FREE(gzPath);
|
||||
} else {
|
||||
video_backend->animation_showDiskChosen(drive);
|
||||
}
|
||||
(*env)->ReleaseStringUTFChars(env, jPath, path);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Activity_nativeEjectDisk(JNIEnv *env, jobject obj, jboolean driveA) {
|
||||
LOG("...");
|
||||
disk6_eject(!driveA);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Constructor
|
||||
|
||||
__attribute__((constructor(CTOR_PRIORITY_LATE)))
|
||||
static void _init_jnihooks(void) {
|
||||
// ...
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Apple // emulator for *ix
|
||||
*
|
||||
* This software package is subject to the GNU General Public License
|
||||
* version 3 or later (your choice) as published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* Copyright 2015 Aaron Culliney
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
typedef enum AndroidTouchJoystickButtonValues {
|
||||
//ANDROID_TOUCHJOY_NONE = 0,
|
||||
ANDROID_TOUCHJOY_BUTTON0 = 1,
|
||||
ANDROID_TOUCHJOY_BUTTON1,
|
||||
ANDROID_TOUCHJOY_BUTTON_BOTH,
|
||||
} AndroidTouchJoystickButtonValues;
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetColor(JNIEnv *env, jclass cls, jint color) {
|
||||
LOG("color : %d", color);
|
||||
#if TESTING
|
||||
color_mode = COLOR;
|
||||
#else
|
||||
if (color < COLOR_NONE || color > COLOR_INTERP) {
|
||||
return;
|
||||
}
|
||||
color_mode = color;
|
||||
|
||||
video_reset();
|
||||
video_setpage(!!(softswitches & SS_SCREEN));
|
||||
video_redraw();
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
|
||||
LOG("enabled : %d", true);
|
||||
// NO-OP ... speaker should always be enabled (but volume could be zero)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetSpeakerVolume(JNIEnv *env, jclass cls, jint goesToTen) {
|
||||
LOG("volume : %d", goesToTen);
|
||||
assert(goesToTen >= 0);
|
||||
sound_volume = goesToTen;
|
||||
#warning FIXME TODO refactor/remove sound_volume ?
|
||||
vm_reinitializeAudio();
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetAudioLatency(JNIEnv *env, jclass cls, jfloat latencySecs) {
|
||||
#if !TESTING
|
||||
LOG("audio latency : %fsecs", latencySecs);
|
||||
assert(cpu_isPaused());
|
||||
audio_setLatency(latencySecs);
|
||||
timing_reinitializeAudio();
|
||||
#endif
|
||||
}
|
||||
|
||||
jboolean Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
|
||||
#if !TESTING
|
||||
LOG("mockingboard enabled : %d", enabled);
|
||||
assert(cpu_isPaused());
|
||||
MB_SetEnabled(enabled);
|
||||
timing_reinitializeAudio();
|
||||
#endif
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetMockingboardVolume(JNIEnv *env, jclass cls, jint goesToTen) {
|
||||
LOG("mockingboard volume : %d", goesToTen);
|
||||
assert(goesToTen >= 0);
|
||||
MB_SetVolumeZeroToTen(goesToTen);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCurrentTouchDevice(JNIEnv *env, jclass cls, jint touchDevice) {
|
||||
LOG("current touch device : %d", touchDevice);
|
||||
assert(touchDevice >= 0 && touchDevice < TOUCH_DEVICE_DEVICE_MAX);
|
||||
switch (touchDevice) {
|
||||
case TOUCH_DEVICE_JOYSTICK:
|
||||
keydriver_setTouchKeyboardOwnsScreen(false);
|
||||
joydriver_setTouchJoystickOwnsScreen(true);
|
||||
joydriver_setTouchVariant(EMULATED_JOYSTICK);
|
||||
video_backend->animation_showTouchJoystick();
|
||||
break;
|
||||
|
||||
case TOUCH_DEVICE_JOYSTICK_KEYPAD:
|
||||
keydriver_setTouchKeyboardOwnsScreen(false);
|
||||
joydriver_setTouchJoystickOwnsScreen(true);
|
||||
joydriver_setTouchVariant(EMULATED_KEYPAD);
|
||||
video_backend->animation_showTouchJoystick();
|
||||
break;
|
||||
|
||||
case TOUCH_DEVICE_KEYBOARD:
|
||||
keydriver_setTouchKeyboardOwnsScreen(true);
|
||||
joydriver_setTouchJoystickOwnsScreen(false);
|
||||
video_backend->animation_showTouchKeyboard();
|
||||
break;
|
||||
|
||||
case TOUCH_DEVICE_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickVisibility(JNIEnv *env, jclass cls, jboolean visibility) {
|
||||
LOG("visibility: %d", visibility);
|
||||
joydriver_setShowControls(visibility);
|
||||
}
|
||||
|
||||
jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCurrentTouchDevice(JNIEnv *env, jclass cls) {
|
||||
LOG("%s", "");
|
||||
if (joydriver_ownsScreen()) {
|
||||
touchjoy_variant_t variant = joydriver_getTouchVariant();
|
||||
if (variant == EMULATED_JOYSTICK) {
|
||||
return TOUCH_DEVICE_JOYSTICK;
|
||||
} else if (variant == EMULATED_KEYPAD) {
|
||||
return TOUCH_DEVICE_JOYSTICK_KEYPAD;
|
||||
}
|
||||
} else if (keydriver_ownsScreen()) {
|
||||
return TOUCH_DEVICE_KEYBOARD;
|
||||
}
|
||||
return TOUCH_DEVICE_NONE;
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchMenuEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
|
||||
LOG("enabled : %d", enabled);
|
||||
interface_setTouchMenuEnabled(enabled);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardLowercaseEnabled(JNIEnv *env, jclass cls, jboolean enabled) {
|
||||
LOG("enabled : %d", enabled);
|
||||
keydriver_setLowercaseEnabled(enabled);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchMenuVisibility(JNIEnv *env, jclass cls, jfloat alpha) {
|
||||
LOG("visibility : %f", alpha);
|
||||
interface_setTouchMenuVisibility(alpha);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchKeyboardVisibility(JNIEnv *env, jclass cls, jfloat inactiveAlpha, jfloat activeAlpha) {
|
||||
LOG("inactive:%f active:%f", inactiveAlpha, activeAlpha);
|
||||
keydriver_setVisibilityWhenOwnsScreen(inactiveAlpha, activeAlpha);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonTypes(JNIEnv *env, jclass cls, jint touchDownButton, jint northButton, jint southButton) {
|
||||
LOG(": %d,%d,%d", touchDownButton, northButton, southButton);
|
||||
|
||||
touchDownButton -= 1;
|
||||
northButton -= 1;
|
||||
southButton -= 1;
|
||||
if (touchDownButton < TOUCH_NONE || touchDownButton > TOUCH_BOTH) {
|
||||
ERRLOG("OOPS, invalid parameter!");
|
||||
return;
|
||||
}
|
||||
if (northButton < TOUCH_NONE || northButton > TOUCH_BOTH) {
|
||||
ERRLOG("OOPS, invalid parameter!");
|
||||
return;
|
||||
}
|
||||
if (southButton < TOUCH_NONE || southButton > TOUCH_BOTH) {
|
||||
ERRLOG("OOPS, invalid parameter!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t rosetteChars[ROSETTE_COLS * ROSETTE_ROWS];
|
||||
int rosetteScancodes[ROSETTE_COLS * ROSETTE_ROWS];
|
||||
rosetteChars[ROSETTE_NORTHWEST] = ' '; rosetteScancodes[ROSETTE_NORTHWEST] = -1;
|
||||
rosetteChars[ROSETTE_NORTH] = (uint8_t)MOUSETEXT_UP; rosetteScancodes[ROSETTE_NORTH] = -1;
|
||||
rosetteChars[ROSETTE_NORTHEAST] = ' '; rosetteScancodes[ROSETTE_NORTHEAST] = -1;
|
||||
rosetteChars[ROSETTE_WEST] = (uint8_t)MOUSETEXT_LEFT; rosetteScancodes[ROSETTE_WEST] = -1;
|
||||
rosetteChars[ROSETTE_CENTER] = '+'; rosetteScancodes[ROSETTE_CENTER] = -1;
|
||||
rosetteChars[ROSETTE_EAST] = (uint8_t)MOUSETEXT_RIGHT; rosetteScancodes[ROSETTE_EAST] = -1;
|
||||
rosetteChars[ROSETTE_SOUTHWEST] = ' '; rosetteScancodes[ROSETTE_SOUTHWEST] = -1;
|
||||
rosetteChars[ROSETTE_SOUTH] = (uint8_t)MOUSETEXT_DOWN; rosetteScancodes[ROSETTE_SOUTH] = -1;
|
||||
rosetteChars[ROSETTE_SOUTHEAST] = ' '; rosetteScancodes[ROSETTE_SOUTHEAST] = -1;
|
||||
joydriver_setTouchAxisTypes(rosetteChars, rosetteScancodes);
|
||||
joydriver_setTouchButtonTypes((touchjoy_button_type_t)touchDownButton, -1, (touchjoy_button_type_t)northButton, -1, (touchjoy_button_type_t)southButton, -1);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickTapDelay(JNIEnv *env, jclass cls, jfloat secs) {
|
||||
LOG("tap delay : %f", secs);
|
||||
joydriver_setTapDelay(secs);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickAxisSensitivity(JNIEnv *env, jclass cls, jfloat multiplier) {
|
||||
LOG("axis sensitivity : %f", multiplier);
|
||||
joydriver_setTouchAxisSensitivity(multiplier);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchJoystickButtonSwitchThreshold(JNIEnv *env, jclass cls, jint delta) {
|
||||
LOG("delta : %d", delta);
|
||||
joydriver_setButtonSwitchThreshold(delta);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetScreenDivision(JNIEnv *env, jclass cls, jfloat division) {
|
||||
LOG("division : %f", division);
|
||||
joydriver_setScreenDivision(division);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetAxisOnLeft(JNIEnv *env, jclass cls, jboolean axisIsOnLeft) {
|
||||
LOG("axis on left : %d", axisIsOnLeft);
|
||||
joydriver_setAxisOnLeft(axisIsOnLeft);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchJoystickSetKeypadTypes(JNIEnv *env, jclass cls,
|
||||
jintArray jRosetteChars, jintArray jRosetteScans, jintArray jButtonsChars, jintArray jButtonsScans)
|
||||
{
|
||||
jint *rosetteChars = (*env)->GetIntArrayElements(env, jRosetteChars, 0);
|
||||
jint *rosetteScans = (*env)->GetIntArrayElements(env, jRosetteScans, 0);
|
||||
jint *buttonsChars = (*env)->GetIntArrayElements(env, jButtonsChars, 0);
|
||||
jint *buttonsScans = (*env)->GetIntArrayElements(env, jButtonsScans, 0);
|
||||
|
||||
LOG("NW:%c/%d, N:%c/%d, NE:%c/%d, ... SWIPEUP:%c/%d",
|
||||
(char)rosetteChars[0], rosetteScans[0], (char)rosetteChars[1], rosetteScans[1], (char)rosetteChars[2], rosetteScans[2],
|
||||
(char)buttonsChars[1], buttonsScans[1]);
|
||||
LOG(" W:%c/%d, C:%c/%d, E:%c/%d, ... TAP:%c/%d",
|
||||
(char)rosetteChars[3], rosetteScans[3], (char)rosetteChars[4], rosetteScans[4], (char)rosetteChars[5], rosetteScans[5],
|
||||
(char)buttonsChars[0], buttonsScans[0]);
|
||||
LOG("SW:%c/%d, S:%c/%d, SE:%c/%d, ... SWIPEDN:%c/%d",
|
||||
(char)rosetteChars[6], rosetteScans[6], (char)rosetteChars[7], rosetteScans[7], (char)rosetteChars[8], rosetteScans[8],
|
||||
(char)buttonsChars[2], buttonsScans[2]);
|
||||
|
||||
// we could just pass these as jcharArray ... but this isn't a tight loop =P
|
||||
uint8_t actualChars[ROSETTE_ROWS * ROSETTE_COLS];
|
||||
for (unsigned int i=0; i<(ROSETTE_ROWS * ROSETTE_COLS); i++) {
|
||||
actualChars[i] = (uint8_t)rosetteChars[i];
|
||||
}
|
||||
joydriver_setTouchAxisTypes(actualChars, rosetteScans);
|
||||
joydriver_setTouchButtonTypes(
|
||||
(touchjoy_button_type_t)buttonsChars[0], buttonsScans[0],
|
||||
(touchjoy_button_type_t)buttonsChars[1], buttonsScans[1],
|
||||
(touchjoy_button_type_t)buttonsChars[2], buttonsScans[2]);
|
||||
|
||||
(*env)->ReleaseIntArrayElements(env, jRosetteChars, rosetteChars, 0);
|
||||
(*env)->ReleaseIntArrayElements(env, jRosetteScans, rosetteScans, 0);
|
||||
(*env)->ReleaseIntArrayElements(env, jButtonsChars, buttonsChars, 0);
|
||||
(*env)->ReleaseIntArrayElements(env, jButtonsScans, buttonsScans, 0);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceBeginCalibrationMode(JNIEnv *env, jclass cls) {
|
||||
LOG("%s", "");
|
||||
if (joydriver_ownsScreen()) {
|
||||
joydriver_beginCalibration();
|
||||
} else if (keydriver_ownsScreen()) {
|
||||
keydriver_beginCalibration();
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeTouchDeviceEndCalibrationMode(JNIEnv *env, jclass cls) {
|
||||
LOG("%s", "");
|
||||
if (joydriver_ownsScreen()) {
|
||||
joydriver_endCalibration();
|
||||
} else if (keydriver_ownsScreen()) {
|
||||
keydriver_endCalibration();
|
||||
}
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetTouchDeviceKeyRepeatThreshold(JNIEnv *env, jclass cls, jfloat threshold) {
|
||||
LOG("threshold : %f", threshold);
|
||||
joydriver_setKeyRepeatThreshold(threshold);
|
||||
}
|
||||
|
||||
jint Java_org_deadc0de_apple2ix_Apple2Preferences_nativeGetCPUSpeed(JNIEnv *env, jclass cls) {
|
||||
LOG("%s", "");
|
||||
return (jint)round(cpu_scale_factor * 100.0);
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeSetCPUSpeed(JNIEnv *env, jclass cls, jint percentSpeed) {
|
||||
LOG("percentSpeed : %d%%", percentSpeed);
|
||||
#if TESTING
|
||||
cpu_scale_factor = CPU_SCALE_FASTEST;
|
||||
cpu_altscale_factor = CPU_SCALE_FASTEST;
|
||||
timing_initialize();
|
||||
#else
|
||||
bool wasPaused = cpu_isPaused();
|
||||
|
||||
if (!wasPaused) {
|
||||
cpu_pause();
|
||||
}
|
||||
|
||||
cpu_scale_factor = percentSpeed/100.0;
|
||||
if (cpu_scale_factor > CPU_SCALE_FASTEST) {
|
||||
cpu_scale_factor = CPU_SCALE_FASTEST;
|
||||
}
|
||||
if (cpu_scale_factor < CPU_SCALE_SLOWEST) {
|
||||
cpu_scale_factor = CPU_SCALE_SLOWEST;
|
||||
}
|
||||
|
||||
if (video_backend->animation_showCPUSpeed) {
|
||||
video_backend->animation_showCPUSpeed();
|
||||
}
|
||||
|
||||
timing_initialize();
|
||||
|
||||
if (!wasPaused) {
|
||||
cpu_resume();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Java_org_deadc0de_apple2ix_Apple2Preferences_nativeLoadTouchKeyboardJSON(JNIEnv *env, jclass cls, jstring j_jsonPath) {
|
||||
const char *jsonPath = (*env)->GetStringUTFChars(env, j_jsonPath, 0);
|
||||
LOG("jsonPath: %s", jsonPath);
|
||||
keydriver_loadAltKbd(jsonPath);
|
||||
(*env)->ReleaseStringUTFChars(env, j_jsonPath, jsonPath);
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
# -----------------------------------------------------------------------------
|
||||
# Common emulator sources and build settings
|
||||
|
||||
APPLE2_SRC_PATH := apple2ix-src
|
||||
|
||||
APPLE2_X86_SRC := \
|
||||
$(APPLE2_SRC_PATH)/x86/glue.S $(APPLE2_SRC_PATH)/x86/cpu.S
|
||||
|
||||
APPLE2_ARM_SRC := \
|
||||
$(APPLE2_SRC_PATH)/arm/glue.S $(APPLE2_SRC_PATH)/arm/cpu.S
|
||||
|
||||
APPLE2_VIDEO_SRC = \
|
||||
$(APPLE2_SRC_PATH)/video/glvideo.c \
|
||||
$(APPLE2_SRC_PATH)/video/glnode.c \
|
||||
$(APPLE2_SRC_PATH)/video/glhudmodel.c \
|
||||
$(APPLE2_SRC_PATH)/video/glalert.c \
|
||||
$(APPLE2_SRC_PATH)/video/gltouchjoy.c \
|
||||
$(APPLE2_SRC_PATH)/video/gltouchjoy_joy.c \
|
||||
$(APPLE2_SRC_PATH)/video/gltouchjoy_kpad.c \
|
||||
$(APPLE2_SRC_PATH)/video/gltouchkbd.c \
|
||||
$(APPLE2_SRC_PATH)/video/gltouchmenu.c \
|
||||
$(APPLE2_SRC_PATH)/video_util/matrixUtil.c \
|
||||
$(APPLE2_SRC_PATH)/video_util/modelUtil.c \
|
||||
$(APPLE2_SRC_PATH)/video_util/sourceUtil.c \
|
||||
$(APPLE2_SRC_PATH)/video_util/vectorUtil.c
|
||||
|
||||
APPLE2_AUDIO_SRC = \
|
||||
$(APPLE2_SRC_PATH)/audio/soundcore.c $(APPLE2_SRC_PATH)/audio/soundcore-opensles.c $(APPLE2_SRC_PATH)/audio/speaker.c \
|
||||
$(APPLE2_SRC_PATH)/audio/mockingboard.c $(APPLE2_SRC_PATH)/audio/AY8910.c
|
||||
|
||||
APPLE2_META_SRC = \
|
||||
$(APPLE2_SRC_PATH)/meta/debug.c $(APPLE2_SRC_PATH)/meta/debugger.c $(APPLE2_SRC_PATH)/meta/opcodes.c \
|
||||
$(APPLE2_SRC_PATH)/meta/lintrace.c $(APPLE2_SRC_PATH)/test/sha1.c $(APPLE2_SRC_PATH)/json_parse.c \
|
||||
$(APPLE2_SRC_PATH)/../externals/jsmn/jsmn.c
|
||||
|
||||
APPLE2_MAIN_SRC = \
|
||||
$(APPLE2_SRC_PATH)/font.c $(APPLE2_SRC_PATH)/rom.c $(APPLE2_SRC_PATH)/misc.c $(APPLE2_SRC_PATH)/display.c $(APPLE2_SRC_PATH)/vm.c \
|
||||
$(APPLE2_SRC_PATH)/timing.c $(APPLE2_SRC_PATH)/zlib-helpers.c $(APPLE2_SRC_PATH)/joystick.c $(APPLE2_SRC_PATH)/keys.c \
|
||||
$(APPLE2_SRC_PATH)/interface.c $(APPLE2_SRC_PATH)/disk.c $(APPLE2_SRC_PATH)/cpu-supp.c \
|
||||
jnihooks.c jniprefs.c androidkeys.c
|
||||
|
||||
APPLE2_OPTIM_CFLAGS := -g -O2
|
||||
APPLE2_BASE_CFLAGS := -DAPPLE2IX=1 -DINTERFACE_TOUCH=1 -DMOBILE_DEVICE=1 -DVIDEO_OPENGL=1 -DDEBUGGER=1 -DAUDIO_ENABLED=1 -std=gnu11 -DPREVENT_TEXTREL=1 -fPIC $(APPLE2_OPTIM_CFLAGS) -I$(APPLE2_SRC_PATH)
|
||||
APPLE2_BASE_LDLIBS := -llog -landroid -lGLESv2 -lz -lOpenSLES
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += cpufeatures
|
||||
|
|
@ -0,0 +1 @@
|
|||
build.sh
|
|
@ -0,0 +1,33 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
|
||||
PACKAGE_NAME := "apple2ix"
|
||||
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
|
||||
include $(COMMON_SOURCES_MK)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Android build config
|
||||
|
||||
LOCAL_MODULE := libapple2ix
|
||||
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testcpu.c
|
||||
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_CPU -DTESTING=1
|
||||
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
|
||||
|
||||
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
|
||||
else
|
||||
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
|
||||
|
||||
# Build a shared library and let Java/Dalvik drive
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
# --OR-- Build an executable so native can drive this show
|
||||
#include $(BUILD_EXECUTABLE)
|
||||
|
||||
$(call import-module, android/cpufeatures)
|
|
@ -0,0 +1 @@
|
|||
build.sh
|
|
@ -0,0 +1,33 @@
|
|||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
PACKAGE_IDENTIFIER := "org.deadc0de.apple2ix"
|
||||
PACKAGE_NAME := "apple2ix"
|
||||
COMMON_SOURCES_MK := $(LOCAL_PATH)/sources.mk
|
||||
include $(COMMON_SOURCES_MK)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Android build config
|
||||
|
||||
LOCAL_MODULE := libapple2ix
|
||||
LOCAL_SRC_FILES := $(APPLE2_SRC_PATH)/test/testcommon.c $(APPLE2_SRC_PATH)/test/testdisk.c
|
||||
LOCAL_CFLAGS := $(APPLE2_BASE_CFLAGS) -DTEST_DISK -DTESTING=1 -DDISK_TRACING=1
|
||||
LOCAL_LDLIBS := $(APPLE2_BASE_LDLIBS)
|
||||
|
||||
# Add assembly files first ... mostly for the benefit of the ARM assembler ...
|
||||
ifeq ($(TARGET_ARCH_ABI),x86)
|
||||
LOCAL_SRC_FILES += $(APPLE2_X86_SRC)
|
||||
else
|
||||
LOCAL_SRC_FILES += $(APPLE2_ARM_SRC)
|
||||
endif
|
||||
|
||||
LOCAL_SRC_FILES += $(APPLE2_MAIN_SRC) $(APPLE2_META_SRC) $(APPLE2_VIDEO_SRC) $(APPLE2_AUDIO_SRC)
|
||||
|
||||
# Build a shared library and let Java/Dalvik drive
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
# --OR-- Build an executable so native can drive this show
|
||||
#include $(BUILD_EXECUTABLE)
|
||||
|
||||
$(call import-module, android/cpufeatures)
|
|
@ -0,0 +1 @@
|
|||
build.sh
|