Merge branch 'develop' ... latest Linux, Android, and Mac variant

This commit is contained in:
Aaron Culliney 2015-11-14 11:10:11 -08:00
commit b445c2dcf1
1088 changed files with 528661 additions and 9130 deletions

26
.gitignore vendored
View File

@ -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

9
Android/.classpath Normal file
View File

@ -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>

1
Android/.idea/.name Normal file
View File

@ -0,0 +1 @@
Android

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

19
Android/.idea/gradle.xml Normal file
View File

@ -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>

46
Android/.idea/misc.xml Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

6
Android/.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="" />
</component>
</project>

19
Android/Android.iml Normal file
View File

@ -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>

1
Android/AndroidManifest.xml Symbolic link
View File

@ -0,0 +1 @@
app/build/intermediates/manifests/full/debug/AndroidManifest.xml

1
Android/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

97
Android/app/app.iml Normal file
View File

@ -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>

41
Android/app/build.gradle Normal file
View File

@ -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'
}

17
Android/app/proguard-rules.pro vendored Normal file
View File

@ -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 *;
#}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

1
Android/app/src/main/assets Symbolic link
View File

@ -0,0 +1 @@
../../../assets

View File

@ -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);
}
});
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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);
}
});
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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.
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
../../../libs

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">&#8595;</string>
<string name="key_esc">[ESC]</string>
<string name="key_left">&#8592;</string>
<string name="key_none">[None]</string>
<string name="key_open_apple">[OpenApple]</string>
<string name="key_ret">[Return]</string>
<string name="key_right">&#8594;</string>
<string name="key_space">[Space]</string>
<string name="key_up">&#8593;</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 &amp; 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">&#8593;,&#8592;,&#8594;,&#8595;, tap spacebar</string>
<string name="keypad_preset_az_left_right_space">A,Z,&#8592;,&#8594;, 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">&#8592;,&#8594;, 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&#8594;</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>

View File

@ -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>

1
Android/assets/disks Symbolic link
View File

@ -0,0 +1 @@
../../../apple2-images-pub/disks

View File

@ -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", "", "", "", ""],
[ "", "", "", "", "", "", "", "", "", ""]
]

View File

@ -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", "" ]
]

View File

@ -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", ""]
]

View File

@ -0,0 +1 @@
../../../src/video/Basic.fsh

View File

@ -0,0 +1 @@
../../../src/video/Basic.vsh

View File

19
Android/build.gradle Normal file
View File

@ -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()
}
}

83
Android/build.xml Normal file
View File

@ -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>

20
Android/gradle.properties Normal file
View File

@ -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

Binary file not shown.

View File

@ -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

164
Android/gradlew vendored Executable file
View File

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

90
Android/gradlew.bat vendored Normal file
View File

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

View File

@ -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

View File

@ -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;

245
Android/jni/androidkeys.c Normal file
View File

@ -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);
}

113
Android/jni/androidkeys.h Normal file
View File

@ -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);

1
Android/jni/apple2ix Symbolic link
View File

@ -0,0 +1 @@
build.sh

1
Android/jni/apple2ix-src Symbolic link
View File

@ -0,0 +1 @@
../../src

61
Android/jni/apple2ix.mk Normal file
View File

@ -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)

1
Android/jni/breakpad Symbolic link
View File

@ -0,0 +1 @@
../../externals/breakpad

1
Android/jni/breakpad.mk Symbolic link
View File

@ -0,0 +1 @@
breakpad/android/google_breakpad/Android.mk

182
Android/jni/build.sh Executable file
View File

@ -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

1
Android/jni/clean Symbolic link
View File

@ -0,0 +1 @@
build.sh

138
Android/jni/jnicrash.c Normal file
View File

@ -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);
}

351
Android/jni/jnihooks.c Normal file
View File

@ -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) {
// ...
}

309
Android/jni/jniprefs.c Normal file
View File

@ -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);
}

47
Android/jni/sources.mk Normal file
View File

@ -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

1
Android/jni/testcpu Symbolic link
View File

@ -0,0 +1 @@
build.sh

33
Android/jni/testcpu.mk Normal file
View File

@ -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)

1
Android/jni/testdisk Symbolic link
View File

@ -0,0 +1 @@
build.sh

33
Android/jni/testdisk.mk Normal file
View File

@ -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)

1
Android/jni/testdisplay Symbolic link
View File

@ -0,0 +1 @@
build.sh

Some files were not shown because too many files have changed in this diff Show More