ANDROID: Fix-up after sync with ScummVM
This commit is contained in:
parent
d420f9dc15
commit
6063865b9e
22 changed files with 2200 additions and 185 deletions
|
@ -58,7 +58,7 @@
|
|||
#include "backends/saves/default/default-saves.h"
|
||||
#include "backends/timer/default/default-timer.h"
|
||||
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
#include "backends/platform/android/android.h"
|
||||
|
||||
const char *android_log_tag = "ResidualVM";
|
||||
|
|
|
@ -253,7 +253,7 @@ public:
|
|||
virtual void updateScreen();
|
||||
virtual Graphics::Surface *lockScreen();
|
||||
virtual void unlockScreen();
|
||||
virtual void setShakePos(int shakeOffset);
|
||||
virtual void setShakePos(int shakeXOffset, int shakeYOffset);
|
||||
virtual void fillScreen(uint32 col);
|
||||
virtual void setFocusRectangle(const Common::Rect& rect);
|
||||
virtual void clearFocusRectangle();
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
#include "common/debug.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
#include "backends/platform/android/asset-archive.h"
|
||||
|
||||
#include <android/asset_manager.h>
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/events.h"
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
#include "engines/engine.h"
|
||||
#include "gui/gui-manager.h"
|
||||
|
|
|
@ -52,6 +52,8 @@ enum {
|
|||
JE_RMB_DOWN = 11,
|
||||
JE_RMB_UP = 12,
|
||||
JE_MOUSE_MOVE = 13,
|
||||
JE_GAMEPAD = 14,
|
||||
JE_JOYSTICK = 15,
|
||||
JE_MMB_DOWN = 16,
|
||||
JE_MMB_UP = 17,
|
||||
JE_TOUCH = 18,
|
||||
|
@ -104,6 +106,25 @@ enum {
|
|||
JKEYCODE_DPAD_CENTER = 23
|
||||
};
|
||||
|
||||
// gamepad
|
||||
enum {
|
||||
JKEYCODE_BUTTON_A = 96,
|
||||
JKEYCODE_BUTTON_B = 97,
|
||||
JKEYCODE_BUTTON_C = 98,
|
||||
JKEYCODE_BUTTON_X = 99,
|
||||
JKEYCODE_BUTTON_Y = 100,
|
||||
JKEYCODE_BUTTON_Z = 101,
|
||||
JKEYCODE_BUTTON_L1 = 102,
|
||||
JKEYCODE_BUTTON_R1 = 103,
|
||||
JKEYCODE_BUTTON_L2 = 104,
|
||||
JKEYCODE_BUTTON_R2 = 105,
|
||||
JKEYCODE_BUTTON_THUMBL = 106,
|
||||
JKEYCODE_BUTTON_THUMBR = 107,
|
||||
JKEYCODE_BUTTON_START = 108,
|
||||
JKEYCODE_BUTTON_SELECT = 109,
|
||||
JKEYCODE_BUTTON_MODE = 110,
|
||||
};
|
||||
|
||||
// meta modifier
|
||||
enum {
|
||||
JMETA_SHIFT = 0x01,
|
||||
|
@ -266,7 +287,6 @@ static const Common::KeyCode jkeymap[] = {
|
|||
Common::KEYCODE_INVALID,
|
||||
Common::KEYCODE_INVALID,
|
||||
Common::KEYCODE_INVALID, // 150
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
#include "graphics/opengl/context.h"
|
||||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
static inline GLfixed xdiv(int numerator, int denominator) {
|
||||
assert(numerator < (1 << 16));
|
||||
|
@ -600,7 +600,7 @@ void OSystem_Android::unlockScreen() {
|
|||
assert(_game_texture->dirty());
|
||||
}
|
||||
|
||||
void OSystem_Android::setShakePos(int shake_offset) {
|
||||
void OSystem_Android::setShakePos(int shakeXOffset, int shakeYOffset) {
|
||||
/* not used in any engine */
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/asset-archive.h"
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
__attribute__ ((visibility("default")))
|
||||
jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
||||
|
@ -85,6 +85,7 @@ jmethodID JNI::_MID_isConnectionLimited = 0;
|
|||
jmethodID JNI::_MID_setWindowCaption = 0;
|
||||
jmethodID JNI::_MID_showVirtualKeyboard = 0;
|
||||
jmethodID JNI::_MID_getSysArchives = 0;
|
||||
jmethodID JNI::_MID_getAllStorageLocations = 0;
|
||||
jmethodID JNI::_MID_initSurface = 0;
|
||||
jmethodID JNI::_MID_deinitSurface = 0;
|
||||
|
||||
|
@ -521,6 +522,7 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
|
|||
FIND_METHOD(, isConnectionLimited, "()Z");
|
||||
FIND_METHOD(, showVirtualKeyboard, "(Z)V");
|
||||
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
|
||||
FIND_METHOD(, getAllStorageLocations, "()[Ljava/lang/String;");
|
||||
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
|
||||
FIND_METHOD(, deinitSurface, "()V");
|
||||
|
||||
|
@ -689,4 +691,38 @@ jstring JNI::getCurrentCharset(JNIEnv *env, jobject self) {
|
|||
return env->NewStringUTF("ISO-8859-1");
|
||||
}
|
||||
|
||||
Common::Array<Common::String> JNI::getAllStorageLocations() {
|
||||
Common::Array<Common::String> *res = new Common::Array<Common::String>();
|
||||
|
||||
JNIEnv *env = JNI::getEnv();
|
||||
|
||||
jobjectArray array =
|
||||
(jobjectArray)env->CallObjectMethod(_jobj, _MID_getAllStorageLocations);
|
||||
|
||||
if (env->ExceptionCheck()) {
|
||||
LOGE("Error finding system archive path");
|
||||
|
||||
env->ExceptionDescribe();
|
||||
env->ExceptionClear();
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
jsize size = env->GetArrayLength(array);
|
||||
for (jsize i = 0; i < size; ++i) {
|
||||
jstring path_obj = (jstring)env->GetObjectArrayElement(array, i);
|
||||
const char *path = env->GetStringUTFChars(path_obj, 0);
|
||||
|
||||
if (path != 0) {
|
||||
res->push_back(path);
|
||||
env->ReleaseStringUTFChars(path_obj, path);
|
||||
}
|
||||
|
||||
env->DeleteLocalRef(path_obj);
|
||||
}
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -78,6 +78,8 @@ public:
|
|||
static inline int writeAudio(JNIEnv *env, jbyteArray &data, int offset,
|
||||
int size);
|
||||
|
||||
static Common::Array<Common::String> getAllStorageLocations();
|
||||
|
||||
private:
|
||||
static JavaVM *_vm;
|
||||
// back pointer to (java) peer instance
|
||||
|
@ -102,6 +104,7 @@ private:
|
|||
static jmethodID _MID_setWindowCaption;
|
||||
static jmethodID _MID_showVirtualKeyboard;
|
||||
static jmethodID _MID_getSysArchives;
|
||||
static jmethodID _MID_getAllStorageLocations;
|
||||
static jmethodID _MID_initSurface;
|
||||
static jmethodID _MID_deinitSurface;
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
MODULE := backends/platform/android
|
||||
|
||||
MODULE_OBJS := \
|
||||
jni.o \
|
||||
jni-android.o \
|
||||
texture.o \
|
||||
asset-archive.o \
|
||||
android.o \
|
||||
gfx.o \
|
||||
events.o \
|
||||
snprintf.o \
|
||||
touchcontrols.o
|
||||
|
||||
# We don't use rules.mk but rather manually update OBJS and MODULE_DIRS.
|
||||
|
|
|
@ -0,0 +1,496 @@
|
|||
package org.residualvm.residualvm;
|
||||
|
||||
import android.os.Environment;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
import android.util.Log;
|
||||
import android.os.Build;
|
||||
|
||||
|
||||
/**
|
||||
* Contains helper methods to get list of available media
|
||||
*/
|
||||
public class ExternalStorage {
|
||||
public static final String SD_CARD = "sdCard";
|
||||
public static final String EXTERNAL_SD_CARD = "externalSdCard";
|
||||
public static final String DATA_DIRECTORY = "ResidualVM data directory";
|
||||
|
||||
|
||||
// Find candidate removable sd card paths
|
||||
// Code reference: https://stackoverflow.com/a/54411385
|
||||
private static final String ANDROID_DIR = File.separator + "Android";
|
||||
|
||||
private static String ancestor(File dir) {
|
||||
// getExternalFilesDir() and getExternalStorageDirectory()
|
||||
// may return something app-specific like:
|
||||
// /storage/sdcard1/Android/data/com.mybackuparchives.android/files
|
||||
// so we want the great-great-grandparent folder.
|
||||
if (dir == null) {
|
||||
return null;
|
||||
} else {
|
||||
String path = dir.getAbsolutePath();
|
||||
int i = path.indexOf(ANDROID_DIR);
|
||||
if (i == -1) {
|
||||
return path;
|
||||
} else {
|
||||
return path.substring(0, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Pattern
|
||||
/** Pattern that SD card device should match */
|
||||
devicePattern = Pattern.compile("/dev/(block/.*vold.*|fuse)|/mnt/.*"),
|
||||
/** Pattern that SD card mount path should match */
|
||||
pathPattern = Pattern.compile("/(mnt|storage|external_sd|extsd|_ExternalSD|Removable|.*MicroSD).*", Pattern.CASE_INSENSITIVE),
|
||||
/** Pattern that the mount path should not match.
|
||||
* 'emulated' indicates an internal storage location, so skip it.
|
||||
* 'asec' is an encrypted package file, decrypted and mounted as a directory. */
|
||||
pathAntiPattern = Pattern.compile(".*(/secure|/asec|/emulated).*"),
|
||||
/** These are expected fs types, including vfat. tmpfs is not OK.
|
||||
* fuse can be removable SD card (as on Moto E or Asus ZenPad), or can be internal (Huawei G610). */
|
||||
fsTypePattern = Pattern.compile(".*(fat|msdos|ntfs|ext[34]|fuse|sdcard|esdfs).*");
|
||||
|
||||
/** Common paths for microSD card. **/
|
||||
private static String[] commonPaths = {
|
||||
// Some of these taken from
|
||||
// https://stackoverflow.com/questions/13976982/removable-storage-external-sdcard-path-by-manufacturers
|
||||
// These are roughly in order such that the earlier ones, if they exist, are more sure
|
||||
// to be removable storage than the later ones.
|
||||
"/mnt/Removable/MicroSD",
|
||||
"/storage/removable/sdcard1", // !< Sony Xperia Z1
|
||||
"/Removable/MicroSD", // Asus ZenPad C
|
||||
"/removable/microsd",
|
||||
"/external_sd", // Samsung
|
||||
"/_ExternalSD", // some LGs
|
||||
"/storage/extSdCard", // later Samsung
|
||||
"/storage/extsdcard", // Main filesystem is case-sensitive; FAT isn't.
|
||||
"/mnt/extsd", // some Chinese tablets, e.g. Zeki
|
||||
"/storage/sdcard1", // If this exists it's more likely than sdcard0 to be removable.
|
||||
"/mnt/extSdCard",
|
||||
"/mnt/sdcard/external_sd",
|
||||
"/mnt/external_sd",
|
||||
"/storage/external_SD",
|
||||
"/storage/ext_sd", // HTC One Max
|
||||
"/mnt/sdcard/_ExternalSD",
|
||||
"/mnt/sdcard-ext",
|
||||
|
||||
"/sdcard2", // HTC One M8s
|
||||
"/sdcard1", // Sony Xperia Z
|
||||
"/mnt/media_rw/sdcard1", // 4.4.2 on CyanogenMod S3
|
||||
"/mnt/sdcard", // This can be built-in storage (non-removable).
|
||||
"/sdcard",
|
||||
"/storage/sdcard0",
|
||||
"/emmc",
|
||||
"/mnt/emmc",
|
||||
"/sdcard/sd",
|
||||
"/mnt/sdcard/bpemmctest",
|
||||
"/mnt/external1",
|
||||
"/data/sdext4",
|
||||
"/data/sdext3",
|
||||
"/data/sdext2",
|
||||
"/data/sdext",
|
||||
"/storage/microsd" //ASUS ZenFone 2
|
||||
|
||||
// If we ever decide to support USB OTG storage, the following paths could be helpful:
|
||||
// An LG Nexus 5 apparently uses usb://1002/UsbStorage/ as a URI to access an SD
|
||||
// card over OTG cable. Other models, like Galaxy S5, use /storage/UsbDriveA
|
||||
// "/mnt/usb_storage",
|
||||
// "/mnt/UsbDriveA",
|
||||
// "/mnt/UsbDriveB",
|
||||
};
|
||||
|
||||
/** Find path to removable SD card. */
|
||||
public static LinkedHashSet<File> findSdCardPath() {
|
||||
String[] mountFields;
|
||||
BufferedReader bufferedReader = null;
|
||||
String lineRead = null;
|
||||
|
||||
/** Possible SD card paths */
|
||||
LinkedHashSet<File> candidatePaths = new LinkedHashSet<File>();
|
||||
|
||||
/** Build a list of candidate paths, roughly in order of preference. That way if
|
||||
* we can't definitively detect removable storage, we at least can pick a more likely
|
||||
* candidate. */
|
||||
|
||||
// Could do: use getExternalStorageState(File path), with and without an argument, when
|
||||
// available. With an argument is available since API level 21.
|
||||
// This may not be necessary, since we also check whether a directory exists and has contents,
|
||||
// which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.
|
||||
|
||||
// I moved hard-coded paths toward the end, but we need to make sure we put the ones in
|
||||
// backwards order that are returned by the OS. And make sure the iterators respect
|
||||
// the order!
|
||||
// This is because when multiple "external" storage paths are returned, it's always (in
|
||||
// experience, but not guaranteed by documentation) with internal/emulated storage
|
||||
// first, removable storage second.
|
||||
|
||||
// Add value of environment variables as candidates, if set:
|
||||
// EXTERNAL_STORAGE, SECONDARY_STORAGE, EXTERNAL_SDCARD_STORAGE
|
||||
// But note they are *not* necessarily *removable* storage! Especially EXTERNAL_STORAGE.
|
||||
// And they are not documented (API) features. Typically useful only for old versions of Android.
|
||||
|
||||
String val = System.getenv("SECONDARY_STORAGE");
|
||||
if (!TextUtils.isEmpty(val)) {
|
||||
addPath(val, candidatePaths);
|
||||
}
|
||||
|
||||
val = System.getenv("EXTERNAL_SDCARD_STORAGE");
|
||||
if (!TextUtils.isEmpty(val)) {
|
||||
addPath(val, candidatePaths);
|
||||
}
|
||||
|
||||
// Get listing of mounted devices with their properties.
|
||||
ArrayList<File> mountedPaths = new ArrayList<File>();
|
||||
try {
|
||||
// Note: Despite restricting some access to /proc (http://stackoverflow.com/a/38728738/423105),
|
||||
// Android 7.0 does *not* block access to /proc/mounts, according to our test on George's Alcatel A30 GSM.
|
||||
bufferedReader = new BufferedReader(new FileReader("/proc/mounts"));
|
||||
|
||||
// Iterate over each line of the mounts listing.
|
||||
while ((lineRead = bufferedReader.readLine()) != null) {
|
||||
// Log.d(ResidualVM.LOG_TAG, "\nMounts line: " + lineRead);
|
||||
mountFields = lineRead.split(" ");
|
||||
|
||||
// columns: device, mountpoint, fs type, options... Example:
|
||||
// /dev/block/vold/179:97 /storage/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1000,gid=1015,fmask=0002,dmask=0002,allow_utime=0020,codepage=cp437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
|
||||
String device = mountFields[0], path = mountFields[1], fsType = mountFields[2];
|
||||
|
||||
// The device, path, and fs type must conform to expected patterns.
|
||||
// mtdblock is internal, I'm told.
|
||||
// Check for disqualifying patterns in the path.
|
||||
// If this mounts line fails our tests, skip it.
|
||||
if (!(devicePattern.matcher(device).matches()
|
||||
&& pathPattern.matcher(path).matches()
|
||||
&& fsTypePattern.matcher(fsType).matches())
|
||||
|| device.contains("mtdblock")
|
||||
|| pathAntiPattern.matcher(path).matches()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO maybe: check options to make sure it's mounted RW?
|
||||
// The answer at http://stackoverflow.com/a/13648873/423105 does.
|
||||
// But it hasn't seemed to be necessary so far in my testing.
|
||||
|
||||
// This line met the criteria so far, so add it to candidate list.
|
||||
addPath(path, mountedPaths);
|
||||
}
|
||||
} catch (IOException ignored) { }
|
||||
finally {
|
||||
if (bufferedReader != null) {
|
||||
try {
|
||||
bufferedReader.close();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
// Append the paths from mount table to candidate list, in reverse order.
|
||||
if (!mountedPaths.isEmpty()) {
|
||||
// See https://stackoverflow.com/a/5374346/423105 on why the following is necessary.
|
||||
// Basically, .toArray() needs its parameter to know what type of array to return.
|
||||
File[] mountedPathsArray = mountedPaths.toArray(new File[mountedPaths.size()]);
|
||||
addAncestors(mountedPathsArray, candidatePaths);
|
||||
}
|
||||
|
||||
// Add hard-coded known common paths to candidate list:
|
||||
addStrings(commonPaths, candidatePaths);
|
||||
|
||||
// If the above doesn't work we could try the following other options, but in my experience they
|
||||
// haven't added anything helpful yet.
|
||||
|
||||
// getExternalFilesDir() and getExternalStorageDirectory() typically something app-specific like
|
||||
// /storage/sdcard1/Android/data/com.mybackuparchives.android/files
|
||||
// so we want the great-great-grandparent folder.
|
||||
|
||||
// This may be non-removable.
|
||||
Log.d(ResidualVM.LOG_TAG, "Environment.getExternalStorageDirectory():");
|
||||
addPath(ancestor(Environment.getExternalStorageDirectory()), candidatePaths);
|
||||
|
||||
// TODO maybe: use getExternalStorageState(File path), with and without an argument, when
|
||||
// available. With an argument is available since API level 21.
|
||||
// This may not be necessary, since we also check whether a directory exists,
|
||||
// which would fail if the external storage state is neither MOUNTED nor MOUNTED_READ_ONLY.
|
||||
|
||||
// A "public" external storage directory. But in my experience it doesn't add anything helpful.
|
||||
// Note that you can't pass null, or you'll get an NPE.
|
||||
final File publicDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
||||
// Take the parent, because we tend to get a path like /pathTo/sdCard/Music.
|
||||
addPath(publicDirectory.getParentFile().getAbsolutePath(), candidatePaths);
|
||||
// EXTERNAL_STORAGE: may not be removable.
|
||||
val = System.getenv("EXTERNAL_STORAGE");
|
||||
if (!TextUtils.isEmpty(val)) {
|
||||
addPath(val, candidatePaths);
|
||||
}
|
||||
|
||||
if (candidatePaths.isEmpty()) {
|
||||
Log.w(ResidualVM.LOG_TAG, "No removable microSD card found.");
|
||||
return candidatePaths;
|
||||
} else {
|
||||
Log.i(ResidualVM.LOG_TAG, "\nFound potential removable storage locations: " + candidatePaths);
|
||||
}
|
||||
|
||||
// Accept or eliminate candidate paths if we can determine whether they're removable storage.
|
||||
// In Lollipop and later, we can check isExternalStorageRemovable() status on each candidate.
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
Iterator<File> itf = candidatePaths.iterator();
|
||||
while (itf.hasNext()) {
|
||||
File dir = itf.next();
|
||||
// handle illegalArgumentException if the path is not a valid storage device.
|
||||
try {
|
||||
if (Environment.isExternalStorageRemovable(dir)) {
|
||||
Log.i(ResidualVM.LOG_TAG, dir.getPath() + " is removable external storage");
|
||||
addPath(dir.getAbsolutePath(), candidatePaths);
|
||||
} else if (Environment.isExternalStorageEmulated(dir)) {
|
||||
Log.d(ResidualVM.LOG_TAG, "Removing emulated external storage dir " + dir);
|
||||
itf.remove();
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.d(ResidualVM.LOG_TAG, "isRemovable(" + dir.getPath() + "): not a valid storage device.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue trying to accept or eliminate candidate paths based on whether they're removable storage.
|
||||
// On pre-Lollipop, we only have singular externalStorage. Check whether it's removable.
|
||||
if (Build.VERSION.SDK_INT >= 9) {
|
||||
File externalStorage = Environment.getExternalStorageDirectory();
|
||||
Log.d(ResidualVM.LOG_TAG, String.format(Locale.ROOT, "findSDCardPath: getExternalStorageDirectory = %s", externalStorage.getPath()));
|
||||
if (Environment.isExternalStorageRemovable()) {
|
||||
// Make sure this is a candidate.
|
||||
// TODO: Does this contains() work? Should we be canonicalizing paths before comparing?
|
||||
if (candidatePaths.contains(externalStorage)) {
|
||||
Log.d(ResidualVM.LOG_TAG, "Using externalStorage dir " + externalStorage);
|
||||
// return externalStorage;
|
||||
addPath(externalStorage.getAbsolutePath(), candidatePaths);
|
||||
}
|
||||
} else if (Build.VERSION.SDK_INT >= 11 && Environment.isExternalStorageEmulated()) {
|
||||
Log.d(ResidualVM.LOG_TAG, "Removing emulated external storage dir " + externalStorage);
|
||||
candidatePaths.remove(externalStorage);
|
||||
}
|
||||
}
|
||||
|
||||
return candidatePaths;
|
||||
}
|
||||
|
||||
|
||||
/** Add each path to the collection. */
|
||||
private static void addStrings(String[] newPaths, LinkedHashSet<File> candidatePaths) {
|
||||
for (String path : newPaths) {
|
||||
addPath(path, candidatePaths);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add ancestor of each File to the collection. */
|
||||
private static void addAncestors(File[] files, LinkedHashSet<File> candidatePaths) {
|
||||
for (int i = files.length - 1; i >= 0; i--) {
|
||||
addPath(ancestor(files[i]), candidatePaths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new candidate directory path to our list, if it's not obviously wrong.
|
||||
* Supply path as either String or File object.
|
||||
* @param strNew path of directory to add
|
||||
*/
|
||||
private static void addPath(String strNew, Collection<File> paths) {
|
||||
// If one of the arguments is null, fill it in from the other.
|
||||
if (strNew != null && !strNew.isEmpty()) {
|
||||
File fileNew = new File(strNew);
|
||||
|
||||
if (!paths.contains(fileNew) &&
|
||||
// Check for paths known not to be removable SD card.
|
||||
// The antipattern check can be redundant, depending on where this is called from.
|
||||
!pathAntiPattern.matcher(strNew).matches()) {
|
||||
|
||||
// Eliminate candidate if not a directory or not fully accessible.
|
||||
if (fileNew.exists() && fileNew.isDirectory() && fileNew.canExecute()) {
|
||||
Log.d(ResidualVM.LOG_TAG, " Adding candidate path " + strNew);
|
||||
paths.add(fileNew);
|
||||
} else {
|
||||
Log.d(ResidualVM.LOG_TAG, String.format(Locale.ROOT, " Invalid path %s: exists: %b isDir: %b canExec: %b canRead: %b",
|
||||
strNew, fileNew.exists(), fileNew.isDirectory(), fileNew.canExecute(), fileNew.canRead()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return True if the external storage is available. False otherwise.
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static String getSdCardPath() {
|
||||
return Environment.getExternalStorageDirectory().getPath() + "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the external storage is writable. False otherwise.
|
||||
*/
|
||||
public static boolean isWritable() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (Environment.MEDIA_MOUNTED.equals(state)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of locations available. Odd elements are names, even are paths
|
||||
*/
|
||||
public static List<String> getAllStorageLocations() {
|
||||
List<String> map = new ArrayList<String>(20);
|
||||
|
||||
List<String> mMounts = new ArrayList<String>(10);
|
||||
List<String> mVold = new ArrayList<String>(10);
|
||||
mMounts.add("/mnt/sdcard");
|
||||
mVold.add("/mnt/sdcard");
|
||||
|
||||
try {
|
||||
File mountFile = new File("/proc/mounts");
|
||||
if (mountFile.exists()) {
|
||||
Scanner scanner = new Scanner(mountFile);
|
||||
while (scanner.hasNext()) {
|
||||
String line = scanner.nextLine();
|
||||
if (line.startsWith("/dev/block/vold/")) {
|
||||
String[] lineElements = line.split(" ");
|
||||
String element = lineElements[1];
|
||||
|
||||
// don't add the default mount path
|
||||
// it's already in the list.
|
||||
if (!element.equals("/mnt/sdcard"))
|
||||
mMounts.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
File voldFile = new File("/system/etc/vold.fstab");
|
||||
if (voldFile.exists()){
|
||||
Scanner scanner = new Scanner(voldFile);
|
||||
while (scanner.hasNext()) {
|
||||
String line = scanner.nextLine();
|
||||
if (line.startsWith("dev_mount")) {
|
||||
String[] lineElements = line.split(" ");
|
||||
String element = lineElements[2];
|
||||
|
||||
if (element.contains(":"))
|
||||
element = element.substring(0, element.indexOf(":"));
|
||||
if (!element.equals("/mnt/sdcard"))
|
||||
mVold.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < mMounts.size(); i++) {
|
||||
String mount = mMounts.get(i);
|
||||
if (!mVold.contains(mount))
|
||||
mMounts.remove(i--);
|
||||
}
|
||||
mVold.clear();
|
||||
|
||||
List<String> mountHash = new ArrayList<String>(10);
|
||||
|
||||
for (String mount : mMounts) {
|
||||
File root = new File(mount);
|
||||
if (root.exists() && root.isDirectory() && root.canRead()) {
|
||||
File[] list = root.listFiles();
|
||||
String hash = "[";
|
||||
if (list != null) {
|
||||
for (File f : list) {
|
||||
hash += f.getName().hashCode() + ":" + f.length() + ", ";
|
||||
}
|
||||
}
|
||||
hash += "]";
|
||||
if (!mountHash.contains(hash)) {
|
||||
String key = SD_CARD + "_" + (map.size() / 2);
|
||||
if (map.size() == 0) {
|
||||
key = SD_CARD;
|
||||
} else if (map.size() == 2) {
|
||||
key = EXTERNAL_SD_CARD;
|
||||
}
|
||||
mountHash.add(hash);
|
||||
map.add(key);
|
||||
map.add(root.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mMounts.clear();
|
||||
|
||||
map.add(DATA_DIRECTORY);
|
||||
map.add(Environment.getDataDirectory().getAbsolutePath());
|
||||
|
||||
// Now go through the external storage
|
||||
if (isAvailable()) { // we can read the External Storage...
|
||||
// Retrieve the primary External Storage:
|
||||
File primaryExternalStorage = Environment.getExternalStorageDirectory();
|
||||
|
||||
//Retrieve the External Storages root directory:
|
||||
String externalStorageRootDir;
|
||||
int count = 0;
|
||||
if ((externalStorageRootDir = primaryExternalStorage.getParent()) == null) { // no parent...
|
||||
String key = primaryExternalStorage.getAbsolutePath();
|
||||
if (!map.contains(key)) {
|
||||
map.add(key); // Make name as directory
|
||||
map.add(key);
|
||||
}
|
||||
} else {
|
||||
File externalStorageRoot = new File(externalStorageRootDir);
|
||||
File[] files = externalStorageRoot.listFiles();
|
||||
|
||||
if (files != null) {
|
||||
for (final File file : files) {
|
||||
if (file.isDirectory() && file.canRead() && (file.listFiles().length > 0)) { // it is a real directory (not a USB drive)...
|
||||
String key = file.getAbsolutePath();
|
||||
if (!map.contains(key)) {
|
||||
map.add(key); // Make name as directory
|
||||
map.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get candidates for removable external storage
|
||||
LinkedHashSet<File> candidateRemovableSdCardPaths = findSdCardPath();
|
||||
for (final File file : candidateRemovableSdCardPaths) {
|
||||
String key = file.getAbsolutePath();
|
||||
if (!map.contains(key)) {
|
||||
map.add(key); // Make name as directory
|
||||
map.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import javax.microedition.khronos.egl.EGLSurface;
|
|||
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("JniMissingFunction")
|
||||
public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
|
||||
|
@ -46,6 +47,7 @@ public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
|
|||
|
||||
// pause the engine and all native threads
|
||||
final public native void setPause(boolean pause);
|
||||
// ResidualVM specific method
|
||||
final public native void enableZoning(boolean enable);
|
||||
// Feed an event to ResidualVM. Safe to call from other threads.
|
||||
final public native void pushEvent(int type, int arg1, int arg2, int arg3,
|
||||
|
@ -62,7 +64,9 @@ public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
|
|||
abstract protected boolean isConnectionLimited();
|
||||
abstract protected void setWindowCaption(String caption);
|
||||
abstract protected void showVirtualKeyboard(boolean enable);
|
||||
abstract protected void showKeyboardControl(boolean enable);
|
||||
abstract protected String[] getSysArchives();
|
||||
abstract protected String[] getAllStorageLocations();
|
||||
|
||||
public ResidualVM(AssetManager asset_manager, SurfaceHolder holder) {
|
||||
_asset_manager = asset_manager;
|
||||
|
@ -142,11 +146,10 @@ public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
|
|||
|
||||
int res = main(_args);
|
||||
|
||||
destroy();
|
||||
|
||||
deinitEGL();
|
||||
deinitAudio();
|
||||
|
||||
destroy();
|
||||
// On exit, tear everything down for a fresh restart next time.
|
||||
System.exit(res);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package org.residualvm.residualvm;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.net.wifi.WifiInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.text.ClipboardManager;
|
||||
|
@ -25,24 +28,56 @@ import android.view.SurfaceView;
|
|||
import android.view.SurfaceHolder;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Button;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class ResidualVMActivity extends Activity {
|
||||
|
||||
public static final String[] REQUIRED_PERMISSIONS = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
||||
static int PERMISSION_REQUEST_REQUIRED_PERMISSIONS = 1001;
|
||||
/* Establish whether the hover events are available */
|
||||
private static boolean _hoverAvailable;
|
||||
|
||||
private ClipboardManager _clipboard;
|
||||
|
||||
/**
|
||||
* Id to identify an external storage read request.
|
||||
*/
|
||||
private static final int MY_PERMISSIONS_REQUEST_READ_EXT_STORAGE = 100; // is an app-defined int constant. The callback method gets the result of the request.
|
||||
|
||||
static {
|
||||
try {
|
||||
MouseHelper.checkHoverAvailable(); // this throws exception if we're on too old version
|
||||
_hoverAvailable = true;
|
||||
} catch (Throwable t) {
|
||||
_hoverAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public View.OnClickListener keyboardBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
toggleKeyboard();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ResidualVM specific code start
|
||||
// The callbacks below implement the action buttons for Grim and EMI.
|
||||
// TODO: Replace by a more generic "touch controls" mechanism
|
||||
private boolean isBtnsShowing = false;
|
||||
|
||||
public View.OnClickListener optionsBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener optionsBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
|
@ -61,54 +96,42 @@ public View.OnClickListener optionsBtnOnClickListener = new View.OnClickListener
|
|||
_residualvm.pushEvent(ResidualVMEvents.JE_KEY, KeyEvent.ACTION_UP, keyCode, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
public View.OnClickListener menuBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener menuBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
emulateKeyPress(KeyEvent.KEYCODE_F1);
|
||||
}
|
||||
};
|
||||
|
||||
public View.OnClickListener inventoryBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener inventoryBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
emulateKeyPress(KeyEvent.KEYCODE_I);
|
||||
}
|
||||
};
|
||||
|
||||
public View.OnClickListener lookAtBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener lookAtBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
emulateKeyPress(KeyEvent.KEYCODE_E);
|
||||
}
|
||||
};
|
||||
|
||||
public View.OnClickListener useBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener useBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
emulateKeyPress(KeyEvent.KEYCODE_ENTER);
|
||||
}
|
||||
};
|
||||
|
||||
public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener() {
|
||||
public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
emulateKeyPress(KeyEvent.KEYCODE_P);
|
||||
}
|
||||
};
|
||||
// ResidualVM specific code end
|
||||
|
||||
/* Establish whether the hover events are available */
|
||||
private static boolean _hoverAvailable;
|
||||
|
||||
private ClipboardManager _clipboard;
|
||||
|
||||
static {
|
||||
try {
|
||||
MouseHelper.checkHoverAvailable(); // this throws exception if we're on too old version
|
||||
_hoverAvailable = true;
|
||||
} catch (Throwable t) {
|
||||
_hoverAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class MyResidualVM extends ResidualVM {
|
||||
|
||||
|
@ -126,9 +149,15 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void displayMessageOnOSD(String msg) {
|
||||
Log.i(LOG_TAG, "OSD: " + msg);
|
||||
Toast.makeText(ResidualVMActivity.this, msg, Toast.LENGTH_LONG).show();
|
||||
protected void displayMessageOnOSD(final String msg) {
|
||||
if (msg != null) {
|
||||
Log.i(LOG_TAG, "MessageOnOSD: " + msg + " " + getCurrentCharset());
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Toast.makeText(ResidualVMActivity.this, msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -174,7 +203,7 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
|
||||
@Override
|
||||
protected boolean isConnectionLimited() {
|
||||
WifiManager wifiMgr = (WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE);
|
||||
if (wifiMgr != null && wifiMgr.isWifiEnabled()) {
|
||||
WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
|
||||
return (wifiInfo == null || wifiInfo.getNetworkId() == -1); //WiFi is on, but it's not connected to any network
|
||||
|
@ -200,60 +229,74 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showKeyboardControl(final boolean enable) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
showKeyboardView(enable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getSysArchives() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getAllStorageLocations() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXT_STORAGE);
|
||||
} else {
|
||||
return _externalStorage.getAllStorageLocations().toArray(new String[0]);
|
||||
}
|
||||
return new String[0]; // an array of zero length
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private MyResidualVM _residualvm;
|
||||
private ResidualVMEvents _events;
|
||||
private MouseHelper _mouseHelper;
|
||||
private Thread _residualvm_thread;
|
||||
|
||||
private boolean checkPermissions() {
|
||||
for (String permission : REQUIRED_PERMISSIONS) {
|
||||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private ExternalStorage _externalStorage;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (checkPermissions()) {
|
||||
launchResidualVM();
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_REQUIRED_PERMISSIONS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Set<String> permissionsToCheck = new HashSet<>(Arrays.asList(REQUIRED_PERMISSIONS));
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
if (grantResults[i] == PackageManager.PERMISSION_GRANTED)
|
||||
permissionsToCheck.remove(permissions[i]);
|
||||
}
|
||||
|
||||
if (permissionsToCheck.isEmpty()) {
|
||||
launchResidualVM();
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private void launchResidualVM() {
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
setContentView(R.layout.main);
|
||||
takeKeyEvents(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_READ_EXT_STORAGE);
|
||||
}
|
||||
|
||||
// This is a common enough error that we should warn about it
|
||||
// explicitly.
|
||||
if (!Environment.getExternalStorageDirectory().canRead()) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.no_sdcard_title)
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setMessage(R.string.no_sdcard)
|
||||
.setNegativeButton(R.string.quit,
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
|
||||
|
||||
main_surface.requestFocus();
|
||||
|
@ -267,7 +310,7 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
saveDir.mkdirs();
|
||||
if (!saveDir.isDirectory()) {
|
||||
// If it doesn't work, resort to the internal app path.
|
||||
savePath = getDir("saves", MODE_WORLD_READABLE).getPath();
|
||||
savePath = getDir("saves", Context.MODE_PRIVATE).getPath();
|
||||
}
|
||||
|
||||
_clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
|
||||
|
@ -291,35 +334,24 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
|
||||
_events = new ResidualVMEvents(this, _residualvm, _mouseHelper);
|
||||
|
||||
// On screen buttons listeners
|
||||
// On screen button listener
|
||||
((ImageView)findViewById(R.id.show_keyboard)).setOnClickListener(keyboardBtnOnClickListener);
|
||||
// ResidualVM specific code start
|
||||
((ImageView)findViewById(R.id.options)).setOnClickListener(optionsBtnOnClickListener);
|
||||
((Button)findViewById(R.id.menu_btn)).setOnClickListener(menuBtnOnClickListener);
|
||||
((Button)findViewById(R.id.inventory_btn)).setOnClickListener(inventoryBtnOnClickListener);
|
||||
((Button)findViewById(R.id.use_btn)).setOnClickListener(useBtnOnClickListener);
|
||||
((Button)findViewById(R.id.pick_up_btn)).setOnClickListener(pickUpBtnOnClickListener);
|
||||
((Button)findViewById(R.id.look_at_btn)).setOnClickListener(lookAtBtnOnClickListener);
|
||||
// ResidualVM specific code end
|
||||
|
||||
main_surface.setOnKeyListener(_events);
|
||||
main_surface.setOnTouchListener(_events);
|
||||
|
||||
_residualvm_thread = new Thread(_residualvm, "ResidualVM");
|
||||
_residualvm_thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.game_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.show_menu:
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
Log.d(ResidualVM.LOG_TAG, "onStart");
|
||||
|
@ -335,6 +367,7 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
|
||||
if (_residualvm != null)
|
||||
_residualvm.setPause(false);
|
||||
showMouseCursor(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -345,6 +378,7 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
|
||||
if (_residualvm != null)
|
||||
_residualvm.setPause(true);
|
||||
showMouseCursor(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -374,6 +408,28 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case MY_PERMISSIONS_REQUEST_READ_EXT_STORAGE:
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// permission was granted
|
||||
Log.i(ResidualVM.LOG_TAG, "Read External Storage permission was granted at Runtime");
|
||||
} else {
|
||||
// permission denied! We won't be able to make use of functionality depending on this permission.
|
||||
Toast.makeText(this, "Until permission is granted, some storage locations may be inaccessible!", Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent e) {
|
||||
if (_events != null)
|
||||
|
@ -383,9 +439,9 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent e) {
|
||||
public boolean onGenericMotionEvent(final MotionEvent e) {
|
||||
if (_events != null)
|
||||
return _events.onTouchEvent(e);
|
||||
return _events.onGenericMotionEvent(e);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -401,4 +457,34 @@ public View.OnClickListener pickUpBtnOnClickListener = new View.OnClickListener(
|
|||
imm.hideSoftInputFromWindow(main_surface.getWindowToken(),
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
|
||||
private void toggleKeyboard() {
|
||||
SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface);
|
||||
InputMethodManager imm = (InputMethodManager)
|
||||
getSystemService(INPUT_METHOD_SERVICE);
|
||||
|
||||
imm.toggleSoftInputFromWindow(main_surface.getWindowToken(),
|
||||
InputMethodManager.SHOW_IMPLICIT,
|
||||
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
|
||||
private void showKeyboardView(boolean show) {
|
||||
ImageView keyboardBtn = (ImageView)findViewById(R.id.show_keyboard);
|
||||
|
||||
if (show)
|
||||
keyboardBtn.setVisibility(View.VISIBLE);
|
||||
else
|
||||
keyboardBtn.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void showMouseCursor(boolean show) {
|
||||
/* Currently hiding the system mouse cursor is only
|
||||
supported on OUYA. If other systems provide similar
|
||||
intents, please add them here as well */
|
||||
Intent intent =
|
||||
new Intent(show?
|
||||
"tv.ouya.controller.action.SHOW_CURSOR" :
|
||||
"tv.ouya.controller.action.HIDE_CURSOR");
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,12 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.InputDevice;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
public class ResidualVMEvents implements
|
||||
android.view.View.OnKeyListener,
|
||||
android.view.View.OnTouchListener,
|
||||
android.view.GestureDetector.OnGestureListener,
|
||||
android.view.GestureDetector.OnDoubleTapListener {
|
||||
|
||||
|
@ -32,6 +34,8 @@ public class ResidualVMEvents implements
|
|||
public static final int JE_RMB_DOWN = 11;
|
||||
public static final int JE_RMB_UP = 12;
|
||||
public static final int JE_MOUSE_MOVE = 13;
|
||||
public static final int JE_GAMEPAD = 14;
|
||||
public static final int JE_JOYSTICK = 15;
|
||||
public static final int JE_MMB_DOWN = 16;
|
||||
public static final int JE_MMB_UP = 17;
|
||||
public static final int JE_TOUCH = 18;
|
||||
|
@ -39,15 +43,11 @@ public class ResidualVMEvents implements
|
|||
public static final int JE_FLING = 20;
|
||||
public static final int JE_QUIT = 0x1000;
|
||||
|
||||
private final int REL_SWIPE_MIN_DISTANCE;
|
||||
private final int REL_SWIPE_THRESHOLD_VELOCITY;
|
||||
|
||||
final protected Context _context;
|
||||
final protected ResidualVM _residualvm;
|
||||
final protected GestureDetector _gd;
|
||||
final protected int _longPress;
|
||||
final protected MouseHelper _mouseHelper;
|
||||
final protected int _width;
|
||||
|
||||
public ResidualVMEvents(Context context, ResidualVM residualvm, MouseHelper mouseHelper) {
|
||||
_context = context;
|
||||
|
@ -56,14 +56,9 @@ public class ResidualVMEvents implements
|
|||
|
||||
_gd = new GestureDetector(context, this);
|
||||
_gd.setOnDoubleTapListener(this);
|
||||
//_gd.setIsLongpressEnabled(false);
|
||||
_gd.setIsLongpressEnabled(false);
|
||||
|
||||
_longPress = ViewConfiguration.getLongPressTimeout();
|
||||
|
||||
DisplayMetrics dm = context.getResources().getDisplayMetrics();
|
||||
REL_SWIPE_MIN_DISTANCE = (int)(120 * dm.densityDpi / 160.0f);
|
||||
REL_SWIPE_THRESHOLD_VELOCITY = (int)(100 * dm.densityDpi / 160.0f);
|
||||
_width = dm.widthPixels;
|
||||
}
|
||||
|
||||
final public void sendQuitEvent() {
|
||||
|
@ -78,6 +73,10 @@ public class ResidualVMEvents implements
|
|||
return true;
|
||||
}
|
||||
|
||||
public boolean onGenericMotionEvent(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final static int MSG_MENU_LONG_PRESS = 1;
|
||||
|
||||
final private Handler keyHandler = new Handler() {
|
||||
|
@ -194,6 +193,25 @@ public class ResidualVMEvents implements
|
|||
(int)(e.getEventTime() - e.getDownTime()),
|
||||
e.getRepeatCount(), 0, 0);
|
||||
return true;
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
case KeyEvent.KEYCODE_BUTTON_C:
|
||||
case KeyEvent.KEYCODE_BUTTON_X:
|
||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||
case KeyEvent.KEYCODE_BUTTON_Z:
|
||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBL:
|
||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||
case KeyEvent.KEYCODE_BUTTON_START:
|
||||
case KeyEvent.KEYCODE_BUTTON_SELECT:
|
||||
case KeyEvent.KEYCODE_BUTTON_MODE:
|
||||
_residualvm.pushEvent(JE_GAMEPAD, action, keyCode,
|
||||
(int)(e.getEventTime() - e.getDownTime()),
|
||||
e.getRepeatCount(), 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
_residualvm.pushEvent(JE_KEY, action, keyCode,
|
||||
|
@ -203,7 +221,9 @@ public class ResidualVMEvents implements
|
|||
return true;
|
||||
}
|
||||
|
||||
final public boolean onTouchEvent(MotionEvent e) {
|
||||
// OnTouchListener
|
||||
@Override
|
||||
final public boolean onTouch(View v, MotionEvent e) {
|
||||
if (_mouseHelper != null) {
|
||||
boolean isMouse = MouseHelper.isMouse(e);
|
||||
if (isMouse) {
|
||||
|
@ -212,35 +232,21 @@ public class ResidualVMEvents implements
|
|||
}
|
||||
}
|
||||
|
||||
final int action = e.getAction();
|
||||
|
||||
_gd.onTouchEvent(e);
|
||||
|
||||
final int action = e.getActionMasked();
|
||||
|
||||
// ACTION_MOVE always returns the first pointer as the "active" one.
|
||||
if (action == MotionEvent.ACTION_MOVE) {
|
||||
for (int idx = 0; idx < e.getPointerCount(); ++idx) {
|
||||
final int pointer = e.getPointerId(idx);
|
||||
|
||||
final int x = (int)e.getX(idx);
|
||||
final int y = (int)e.getY(idx);
|
||||
|
||||
_residualvm.pushEvent(JE_TOUCH, pointer, action, x, y, 0, 0);
|
||||
}
|
||||
} else {
|
||||
final int idx = e.getActionIndex();
|
||||
final int pointer = e.getPointerId(idx);
|
||||
|
||||
final int x = (int)e.getX(idx);
|
||||
final int y = (int)e.getY(idx);
|
||||
|
||||
_residualvm.pushEvent(JE_TOUCH, pointer, action, x, y, 0, 0);
|
||||
}
|
||||
|
||||
// constants from APIv5:
|
||||
// (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT
|
||||
final int pointer = (action & 0xff00) >> 8;
|
||||
|
||||
if (pointer > 0) {
|
||||
_residualvm.pushEvent(JE_MULTI, pointer, action & 0xff, // ACTION_MASK
|
||||
(int)e.getX(), (int)e.getY(), 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _gd.onTouchEvent(e);
|
||||
}
|
||||
|
||||
// OnGestureListener
|
||||
@Override
|
||||
final public boolean onDown(MotionEvent e) {
|
||||
|
@ -252,21 +258,11 @@ public class ResidualVMEvents implements
|
|||
final public boolean onFling(MotionEvent e1, MotionEvent e2,
|
||||
float velocityX, float velocityY) {
|
||||
return false;
|
||||
// if (e1.getX() < 0.4 * _width
|
||||
// || Math.abs(e1.getX() - e2.getX()) < REL_SWIPE_MIN_DISTANCE
|
||||
// || velocityX < REL_SWIPE_THRESHOLD_VELOCITY
|
||||
// || Math.abs(e1.getY() - e2.getY()) < REL_SWIPE_MIN_DISTANCE
|
||||
// || velocityY < REL_SWIPE_THRESHOLD_VELOCITY)
|
||||
// return false;
|
||||
//
|
||||
// _residualvm.pushEvent(JE_FLING, (int)e1.getX(), (int)e1.getY(),
|
||||
// (int)e2.getX(), (int)e2.getY(), 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public void onLongPress(MotionEvent e) {
|
||||
_residualvm.pushEvent(JE_LONG, (int)e.getX(), (int)e.getY(),
|
||||
0, 0, 0, 0);
|
||||
// disabled, interferes with drag&drop
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -274,6 +270,7 @@ public class ResidualVMEvents implements
|
|||
float distanceX, float distanceY) {
|
||||
_residualvm.pushEvent(JE_SCROLL, (int)e1.getX(), (int)e1.getY(),
|
||||
(int)e2.getX(), (int)e2.getY(), 0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
48
backends/platform/android/portdefs.h
Normal file
48
backends/platform/android/portdefs.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PORTDEFS_H_
|
||||
#define _PORTDEFS_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <new>
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
// This is defined in snprintf.c
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
int rpl_vsnprintf(char *text, size_t maxlen, const char *fmt, va_list ap);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#define vsnprintf rpl_vsnprintf
|
||||
|
||||
#endif // _PORTDEFS_H_
|
1323
backends/platform/android/snprintf.cpp
Normal file
1323
backends/platform/android/snprintf.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
@ -50,7 +50,7 @@
|
|||
|
||||
#include "backends/platform/android/texture.h"
|
||||
#include "backends/platform/android/android.h"
|
||||
#include "backends/platform/android/jni.h"
|
||||
#include "backends/platform/android/jni-android.h"
|
||||
|
||||
// Supported GL extensions
|
||||
static bool npot_supported = false;
|
||||
|
|
BIN
dists/android/res/drawable-hdpi/ic_action_keyboard.png
Executable file
BIN
dists/android/res/drawable-hdpi/ic_action_keyboard.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 395 B |
BIN
dists/android/res/drawable-mdpi/ic_action_keyboard.png
Executable file
BIN
dists/android/res/drawable-mdpi/ic_action_keyboard.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 394 B |
BIN
dists/android/res/drawable-xhdpi/ic_action_keyboard.png
Executable file
BIN
dists/android/res/drawable-xhdpi/ic_action_keyboard.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 553 B |
BIN
dists/android/res/drawable-xxhdpi/ic_action_keyboard.png
Executable file
BIN
dists/android/res/drawable-xxhdpi/ic_action_keyboard.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 628 B |
|
@ -26,7 +26,7 @@
|
|||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp" />
|
||||
|
||||
<HorizontalScrollView
|
||||
<HorizontalScrollView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/btns_scrollview"
|
||||
|
@ -80,6 +80,14 @@
|
|||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginRight="5dp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/show_keyboard"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:src="@drawable/ic_action_keyboard" />
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<item android:id="@+id/show_menu" android:titleCondensed="Menu" android:title="Show menu..."></item>
|
||||
|
||||
|
||||
</menu>
|
Loading…
Add table
Add a link
Reference in a new issue