Disentangle new-school and old-school disk selection and misc cleanup

This commit is contained in:
Aaron Culliney 2017-06-07 18:18:39 -07:00
parent 2a263e2418
commit dd0de51d64
8 changed files with 424 additions and 76 deletions

View File

@ -229,7 +229,24 @@ public class Apple2Activity extends Activity implements Apple2DiskChooserActivit
if (sDisksChosen != null && mDisksMenu != null) {
if (sDisksChosen.pfd != null) {
mDisksMenu.showDiskInsertionAlertDialog("title", sDisksChosen);
String name = sDisksChosen.name;
final String[] prefices = {"content://com.android.externalstorage.documents/document", "content://com.android.externalstorage.documents", "content://com.android.externalstorage.documents", "content://"};
for (String prefix : prefices) {
if (name.startsWith(prefix)) {
name = name.substring(prefix.length());
break;
}
}
// strip out URL-encoded '/' directory separators
String nameLower = name.toLowerCase();
int idx = nameLower.lastIndexOf("%2f", /*fromIndex:*/name.length()-3);
if (idx >= 0) {
name = name.substring(idx + 3);
}
mDisksMenu.showDiskInsertionAlertDialog(name, sDisksChosen);
}
sDisksChosen = null;
}

View File

@ -157,7 +157,8 @@ public class Apple2DiskChooserActivity extends Activity {
@Override
public void finish() {
sDiskChooserIsChoosing.set(false);
sDisksCallback.onDisksChosen(new DiskArgs(chosenUri, chosenPfd));
String name = chosenUri == null ? "" : chosenUri.toString();
sDisksCallback.onDisksChosen(new DiskArgs(name, chosenUri, chosenPfd));
super.finish();
}
@ -171,11 +172,13 @@ public class Apple2DiskChooserActivity extends Activity {
}
class DiskArgs {
public String name;
public String path;
public Uri uri;
public ParcelFileDescriptor pfd;
public DiskArgs(Uri uri, ParcelFileDescriptor pfd) {
public DiskArgs(String name, Uri uri, ParcelFileDescriptor pfd) {
this.name = name;
this.uri = uri;
this.pfd = pfd;
}

View File

@ -25,11 +25,13 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
@ -155,6 +157,17 @@ public class Apple2DisksMenu implements Apple2MenuView {
return "driveBInsertedDiskGZ";
}
@Override
public Object getPrefDefault() {
return true;
}
},
USE_NEWSCHOOL_DISK_SELECTION {
@Override
public String getPrefKey() {
return "useNewSchoolDiskSelection";
}
@Override
public Object getPrefDefault() {
return true;
@ -201,6 +214,28 @@ public class Apple2DisksMenu implements Apple2MenuView {
}
});
{
final Button ejectButton1 = (Button) mDisksView.findViewById(R.id.ejectButton1);
ejectButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ejectDisk(/*isDriveA:*/true);
dynamicSetup();
}
});
}
{
final Button ejectButton2 = (Button) mDisksView.findViewById(R.id.ejectButton2);
ejectButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ejectDisk(/*isDriveA:*/false);
dynamicSetup();
}
});
}
Apple2Utils.getExternalStorageDirectory(activity);
}
@ -296,6 +331,10 @@ public class Apple2DisksMenu implements Apple2MenuView {
}
if (imageName.startsWith(EXTERNAL_CHOOSER_SENTINEL)) {
if (!Apple2Utils.isExternalStorageAccessible(activity)) {
// disallow access if we cannot access external storage
throw new Exception("External storage not accessible");
}
if (diskArgs.pfd == null) {
if (diskArgs.uri == null) {
String uriString = imageName.substring(EXTERNAL_CHOOSER_SENTINEL.length());
@ -304,7 +343,7 @@ public class Apple2DisksMenu implements Apple2MenuView {
diskArgs.pfd = Apple2DiskChooserActivity.openFileDescriptorFromUri(activity, diskArgs.uri);
}
int fd = diskArgs.pfd.getFd();
int fd = diskArgs.pfd.getFd(); // NPE thrown if diskArgs.pfd is null
map.put("fd", fd);
} else {
File file = new File(imageName);
@ -331,14 +370,12 @@ public class Apple2DisksMenu implements Apple2MenuView {
String jsonString = nativeChooseDisk(map.toString());
if (diskArgs.pfd != null) {
try {
diskArgs.pfd.close();
} catch (IOException ioe) {
Log.e(TAG, "Error attempting to close PFD : " + ioe);
}
diskArgs.pfd = null;
try {
diskArgs.pfd.close(); // at this point diskArgs.pfd !null
} catch (IOException ioe) {
Log.e(TAG, "Error attempting to close PFD : " + ioe);
}
diskArgs.pfd = null;
map = new JSONObject(jsonString);
boolean inserted = map.getBoolean("inserted");
@ -479,11 +516,136 @@ public class Apple2DisksMenu implements Apple2MenuView {
private void dynamicSetup() {
final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
disksList.setEnabled(true);
final boolean useNewschoolSelection = (boolean) Apple2Preferences.getJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION);
final CheckBox newschoolSelection = (CheckBox) mDisksView.findViewById(R.id.newschoolDiskSelectionButton);
newschoolSelection.setChecked(useNewschoolSelection);
newschoolSelection.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Apple2Preferences.setJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION, newschoolSelection.isChecked());
dynamicSetup();
}
});
final boolean isKitKatOrBetter = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);
final boolean includeExternalFileChooser = Apple2Utils.isExternalStorageAccessible(mActivity) && isKitKatOrBetter;
final View newschoolChooser = mDisksView.findViewById(R.id.disk_selection_newschool_chooser);
newschoolChooser.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final boolean alreadyChoosing = Apple2DiskChooserActivity.sDiskChooserIsChoosing.getAndSet(true);
if (alreadyChoosing) {
return;
}
Intent chooserIntent = new Intent(mActivity, Apple2DiskChooserActivity.class);
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION/* | Intent.FLAG_ACTIVITY_CLEAR_TOP */);
Apple2DiskChooserActivity.sDisksCallback = mActivity;
mActivity.startActivity(chooserIntent);
}
});
final Button ejectButton1 = (Button) mDisksView.findViewById(R.id.ejectButton1);
final Button ejectButton2 = (Button) mDisksView.findViewById(R.id.ejectButton2);
if (!includeExternalFileChooser || !useNewschoolSelection) {
disksList.setEnabled(true);
disksList.setVisibility(View.VISIBLE);
newschoolChooser.setVisibility(View.INVISIBLE);
ejectButton1.setVisibility(View.VISIBLE);
ejectButton2.setVisibility(View.VISIBLE);
for (int i = 0; i < 2; i++) {
LinearLayout layout = (LinearLayout) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_driveA_layout : R.id.a2_newschool_driveB_layout);
layout.setVisibility(View.INVISIBLE);
}
if (!includeExternalFileChooser) {
newschoolSelection.setVisibility(View.INVISIBLE);
}
Apple2Preferences.setJSONPref(SETTINGS.USE_NEWSCHOOL_DISK_SELECTION, false);
oldschoolDynamicSetup();
return;
}
disksList.setEnabled(false);
disksList.setVisibility(View.INVISIBLE);
newschoolChooser.setVisibility(View.VISIBLE);
ejectButton1.setVisibility(View.INVISIBLE);
ejectButton2.setVisibility(View.INVISIBLE);
// new external file chooser activity can allow navigation to restricted external SD Card(s) ...
newschoolSelection.setVisibility(View.VISIBLE);
for (int i = 0; i < 2; i++) {
String imageName = null;
do {
String diskPath = (String) Apple2Preferences.getJSONPref((i == 0) ? SETTINGS.CURRENT_DISK_PATH_A : SETTINGS.CURRENT_DISK_PATH_B);
if (diskPath == null || diskPath.equals("")) {
break;
}
Uri uri = Uri.parse(diskPath);
if (uri == null) {
break;
}
diskPath = uri.getPath();
int idx = diskPath.lastIndexOf("/");
if (idx < 0) {
break;
}
imageName = diskPath.substring(idx + 1);
} while (false);
LinearLayout layout = (LinearLayout) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_driveA_layout : R.id.a2_newschool_driveB_layout);
LinearLayout widgetLayout = (LinearLayout) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_diskA_widget_frame : R.id.a2_newschool_diskB_widget_frame);
if (widgetLayout.getChildCount() > 0) {
// layout cells appear to be reused when scrolling into view ... make sure we start with clear hierarchy
widgetLayout.removeAllViews();
}
if (imageName == null || imageName.equals("")) {
layout.setVisibility(View.INVISIBLE);
} else {
layout.setVisibility(View.VISIBLE);
boolean readOnly = (boolean) Apple2Preferences.getJSONPref((i == 0) ? SETTINGS.CURRENT_DISK_PATH_A_RO : SETTINGS.CURRENT_DISK_PATH_B_RO);
imageName = "(" + mActivity.getResources().getString((readOnly ? R.string.disk_read_only : R.string.disk_read_write)) + "): " + imageName;
TextView textView = (TextView) mDisksView.findViewById((i == 0) ? R.id.a2_newschool_diskA : R.id.a2_newschool_diskB);
textView.setText(imageName);
String eject = mActivity.getResources().getString(R.string.disk_eject);
Button ejectButton = new Button(mActivity);
ejectButton.setText(eject);
final boolean isDriveA = (i == 0);
ejectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ejectDisk(isDriveA);
dynamicSetup();
}
});
widgetLayout.addView(ejectButton);
}
}
}
private void oldschoolDynamicSetup() {
final ListView disksList = (ListView) mDisksView.findViewById(R.id.listView_settings);
String disksDir = pathStackAsDirectory();
final boolean isRootPath = (disksDir == null);
final File extStorageDir = Apple2Utils.getExternalStorageDirectory(mActivity);
if (isRootPath) {
disksDir = Apple2Utils.getDataDir(mActivity) + File.separator + "disks"; // default path
}
@ -532,31 +694,15 @@ public class Apple2DisksMenu implements Apple2MenuView {
}
}
int off = 0;
final boolean includeExternalStoragePath = (extStorageDir != null && isRootPath);
if (includeExternalStoragePath) {
++off;
}
final boolean includeExternalFileChooser = isRootPath && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT);
if (includeExternalFileChooser) {
++off;
}
final int offset = off;
final boolean includeExternalStoragePath = (Apple2Utils.isExternalStorageAccessible(mActivity) && isRootPath);
final int offset = includeExternalStoragePath ? 1 : 0;
final String[] fileNames = new String[files.length + offset];
final String[] filePaths = new String[files.length + offset];
final boolean[] isDirectory = new boolean[files.length + offset];
int idx = 0;
// external file chooser can allow navigation to restricted external SD Card
if (includeExternalFileChooser) {
filePaths[idx] = EXTERNAL_CHOOSER_SENTINEL;
fileNames[idx] = mActivity.getResources().getString(R.string.file_chooser);
isDirectory[idx] = true;
++idx;
}
// first external storage link should be /sdcard/apple2ix to encourage this form of organization
if (includeExternalStoragePath) {
filePaths[idx] = Apple2Utils.getRealExternalStorageDirectory(mActivity).getAbsolutePath();
fileNames[idx] = mActivity.getResources().getString(R.string.storage);
@ -636,21 +782,6 @@ public class Apple2DisksMenu implements Apple2MenuView {
@Override
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
if (isDirectory[position]) {
if (filePaths[position].equals(EXTERNAL_CHOOSER_SENTINEL)) {
final boolean alreadyChoosing = Apple2DiskChooserActivity.sDiskChooserIsChoosing.getAndSet(true);
if (alreadyChoosing) {
return;
}
Intent chooserIntent = new Intent(mActivity, Apple2DiskChooserActivity.class);
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION/* | Intent.FLAG_ACTIVITY_CLEAR_TOP */);
Apple2DiskChooserActivity.sDisksCallback = mActivity;
mActivity.startActivity(chooserIntent);
return;
}
Log.d(TAG, "Descending to path : " + filePaths[position]);
if (parentIsRootPath && !new File(filePaths[position]).isAbsolute()) {
pushPathStack(parentDisksDir + File.separator + filePaths[position]);

View File

@ -343,9 +343,10 @@ public class Apple2MainMenu {
final String[] readOnlyKeys = new String[]{"readOnlyA", "readOnlyB"};
final String[] fdKeys = new String[]{"fdA", "fdB"};
ParcelFileDescriptor[] pfds = { null, null };
ParcelFileDescriptor[] pfds = {null, null};
for (int i = 0; i < 2; i++) {
String diskPath = map.getString(diskPathKeys[i]);
boolean readOnly = map.getBoolean(readOnlyKeys[i]);
@ -362,13 +363,16 @@ public class Apple2MainMenu {
Uri uri = Uri.parse(uriString);
pfds[i] = Apple2DiskChooserActivity.openFileDescriptorFromUri(mActivity, uri);
int fd = pfds[i].getFd();
map.put(fdKeys[i], fd);
if (pfds[i] == null) {
Log.e(TAG, "Did not find URI for drive #" + i + " specified in emulator.state file : " + diskPath);
} else {
int fd = pfds[i].getFd();
map.put(fdKeys[i], fd);
}
} else {
boolean exists = new File(diskPath).exists();
if (!exists) {
Log.e(TAG, "Did not find path(s) for drive #" + i + " specified in emulator.state file : " + diskPath);
Log.e(TAG, "Did not find path for drive #" + i + " specified in emulator.state file : " + diskPath);
}
}
}

View File

@ -188,6 +188,11 @@ public class Apple2Utils {
return sRealExternalFilesDir;
}
public static boolean isExternalStorageAccessible(Apple2Activity activity) {
getExternalStorageDirectory(activity);
return (sRealExternalFilesDir != null) && (new File(sRealExternalFilesDir.getAbsolutePath()).listFiles() != null);
}
// 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 =)

View File

@ -43,6 +43,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/a2preference_title"
android:layout_alignLeft="@id/a2preference_title"
android:layout_alignStart="@id/a2preference_title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:maxLines="4" />

View File

@ -1,39 +1,223 @@
<?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"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
android:layout_height="fill_parent"
android:layout_marginBottom="5dip"
android:layout_marginLeft="5dip"
android:layout_marginStart="5dip"
android:layout_marginRight="5dip"
android:layout_marginEnd="5dip"
android:layout_marginTop="5dip"
android:background="@color/black"
android:layout_weight="1">
<LinearLayout
android:background="@color/black"
android:orientation="horizontal"
android:baselineAligned="false"
android:id="@+id/disks_selection_header"
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" />
android:layout_height="wrap_content"
android:background="@color/black"
android:baselineAligned="false"
android:orientation="horizontal">
<Button
android:id="@+id/ejectButton1"
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" />
android:drawableLeft="@android:drawable/ic_menu_save"
android:drawableStart="@android:drawable/ic_menu_save"
android:text="@string/header_eject_1" />
<Button
android:id="@+id/ejectButton2"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@android:drawable/ic_menu_save"
android:drawableStart="@android:drawable/ic_menu_save"
android:text="@string/header_eject_2" />
<!-- FIXME : spacer, prolly a better way ... -->
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />
<Button
android:id="@+id/cancelButton"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableEnd="@android:drawable/ic_menu_close_clear_cancel"
android:drawableRight="@android:drawable/ic_menu_close_clear_cancel" />
</LinearLayout>
<CheckBox
android:id="@+id/newschoolDiskSelectionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/disks_selection_header"
android:layout_alignStart="@id/disks_selection_header"
android:layout_below="@id/disks_selection_header"
android:layout_marginTop="10dip"
android:text="@string/disk_selection_newschoool" />
<ListView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:id="@+id/listView_settings"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignLeft="@id/disks_selection_header"
android:layout_alignStart="@id/disks_selection_header"
android:layout_below="@id/newschoolDiskSelectionButton"
android:visibility="invisible"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/disk_selection_newschool_chooser"
android:layout_alignLeft="@id/disks_selection_header"
android:layout_alignStart="@id/disks_selection_header"
android:layout_below="@id/newschoolDiskSelectionButton"
android:layout_marginLeft="5dip"
android:layout_marginStart="5dip"
android:layout_marginRight="5dip"
android:layout_marginEnd="5dip"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_marginTop="20dip"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@+string/file_chooser"
android:textAppearance="?android:attr/textAppearanceLarge" />
<!-- FIXME : spacer, prolly a better way ... -->
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="1" />
<ImageView
android:src="@android:drawable/ic_menu_save"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/a2_newschool_driveA_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/disk_selection_newschool_chooser"
android:layout_alignStart="@id/disk_selection_newschool_chooser"
android:layout_below="@id/disk_selection_newschool_chooser"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingRight="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="5dip"
android:layout_weight="1">
<TextView
android:id="@+id/a2_newschool_driveA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@+string/diskA"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/a2_newschool_diskA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveA"
android:layout_alignStart="@id/a2_newschool_driveA"
android:layout_below="@id/a2_newschool_driveA"
android:maxLines="4"
android:text="someLongSelectedDiskName.dsk.gz"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<!-- Custom preference should place its actual preference widget here. -->
<LinearLayout
android:id="@+id/a2_newschool_diskA_widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
android:orientation="vertical" />
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/a2_newschool_driveB_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveA_layout"
android:layout_alignStart="@id/a2_newschool_driveA_layout"
android:layout_below="@id/a2_newschool_driveA_layout"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingRight="?android:attr/scrollbarSize">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dip"
android:layout_marginRight="5dip"
android:layout_marginTop="5dip"
android:layout_weight="1">
<TextView
android:id="@+id/a2_newschool_driveB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:text="@+string/diskB"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/a2_newschool_diskB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/a2_newschool_driveB"
android:layout_alignStart="@id/a2_newschool_driveB"
android:layout_below="@id/a2_newschool_driveB"
android:maxLines="4"
android:text="someLongSelectedDiskName.dsk.gz"
android:textAppearance="?android:attr/textAppearanceSmall" />
</RelativeLayout>
<!-- Custom preference should place its actual preference widget here. -->
<LinearLayout
android:id="@+id/a2_newschool_diskB_widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
android:orientation="vertical" />
</LinearLayout>
</RelativeLayout>

View File

@ -40,12 +40,15 @@
<string name="disk_insert_could_not_read">Sorry, 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="disk_selection_newschoool">Use system file chooser</string>
<string name="disk_show_operation">Show Disk ][ operations</string>
<string name="disk_show_operation_summary">Shows when disk drives are reading or writing</string>
<string name="emulation_continue">Continue…</string>
<string name="emulation_disks">Load disk image</string>
<string name="file_chooser">System choose disk…</string>
<string name="file_chooser">Choose disk…</string>
<string name="header_disks">Insert disk:</string>
<string name="header_eject_1">Eject 1</string>
<string name="header_eject_2">Eject 2</string>
<string name="input_current">Current touch device</string>
<string name="input_current_summary">Choose current touch device</string>
<string name="joystick">Joystick</string>