mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e3cd38a8f | ||
|
|
50d6b14ff3 | ||
|
|
4c53a5b85f | ||
|
|
f1d436b144 | ||
|
|
5939e41da6 | ||
|
|
7079da6e90 | ||
|
|
1680e885f2 | ||
|
|
b31737d4f7 | ||
|
|
270f974926 | ||
|
|
92cc37560a | ||
|
|
3abdbcb53b | ||
|
|
b7cacdc6da |
@@ -399,3 +399,7 @@ FodyWeavers.xsd
|
||||
# versioning header
|
||||
src/platform/git_version.h
|
||||
CMakeUserPresets.json
|
||||
|
||||
# Local configs for AI agents
|
||||
.aider*
|
||||
.gemini
|
||||
|
||||
@@ -102,6 +102,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"src/font_manager.h"
|
||||
"src/game_config.cc"
|
||||
"src/game_config.h"
|
||||
"src/game_config_migration.cc"
|
||||
"src/game_config_migration.h"
|
||||
"src/game_dialog.cc"
|
||||
"src/game_dialog.h"
|
||||
"src/game_memory.cc"
|
||||
@@ -172,6 +174,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC
|
||||
"src/obj_types.h"
|
||||
"src/object.cc"
|
||||
"src/object.h"
|
||||
"src/opcode_context.cc"
|
||||
"src/opcode_context.h"
|
||||
"src/options.cc"
|
||||
"src/options.h"
|
||||
"src/palette.cc"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Contributing
|
||||
|
||||
This file is currently a shell to be filled in over time. For now, it only captures project-specific maintenance notes that are easy to miss.
|
||||
|
||||
## Updating SDL
|
||||
|
||||
SDL is pinned for native builds in `third_party/sdl2/CMakeLists.txt`. Right now, Android also relies on checked-in Java bindings in `os/android/app/src/main/java/org/libsdl/app`, and those bindings must match the SDL version fetched by CMake.
|
||||
|
||||
When updating SDL:
|
||||
|
||||
1. Update the SDL tag in `third_party/sdl2/CMakeLists.txt`.
|
||||
2. Run an Android build once so Gradle/CMake fetch the new SDL source tree.
|
||||
3. Copy the Android Java bindings from the fetched SDL checkout into `os/android/app/src/main/java/org/libsdl/app`.
|
||||
Source path pattern:
|
||||
`os/android/app/.cxx/<Variant>/<Hash>/arm64-v8a/_deps/sdl2-src/android-project/app/src/main/java/org/libsdl/app`
|
||||
4. Rebuild Android and confirm the SDL sync guard passes.
|
||||
|
||||
The Android build contains a guard in `os/android/app/build.gradle` that fails if the checked-in Java bindings drift from the fetched native SDL source.
|
||||
|
||||
This is a hack, not a good long-term setup. The Java bindings are still duplicated in the repo instead of being sourced from the same SDL tree as the native build. That should be improved so Android does not depend on a manual copy/sync step.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Fallout 2 Community Edition - Gemini Instructions
|
||||
|
||||
## Project Overview
|
||||
Fallout 2 Community Edition is a re-implementation of Fallout 2. It is written in C++ and uses CMake as its build system.
|
||||
|
||||
## Project Rules
|
||||
- Only search for source code and symbols inside the `src/` directory.
|
||||
|
||||
## Coding Standards & Conventions
|
||||
- **Language**: C++17.
|
||||
- **Style**: Follow WebKit style as defined in `.clang-format`.
|
||||
- Use `clang-format` before committing any changes.
|
||||
- Short `if` statements without `else` can be on a single line.
|
||||
- Fix namespace comments.
|
||||
- **File Naming**:
|
||||
- Source files: `.cc`
|
||||
- Header files: `.h`
|
||||
- **Architectural Constraint**:
|
||||
- Don't use complicated and rarely used C++ features, unless they are well encapsulated within reusable code.
|
||||
- Avoid large-scale refactorings. The project aims to reconcile changes from the Reference Edition, and major structural changes make this difficult.
|
||||
- **Integration Priority**: Integrating features and fixes from Sfall is a top priority.
|
||||
|
||||
## Build System (CMake)
|
||||
- **Version**: Requires CMake 3.18 or higher.
|
||||
- **Dependencies**: Uses vendored third-party libraries by default (`FALLOUT_VENDORED=ON`).
|
||||
- **Target**: The main executable is `fallout2-ce`.
|
||||
- **Platforms**: Supports Windows, Linux, macOS, Android, iOS, and Emscripten (WebAssembly).
|
||||
|
||||
## Project Structure
|
||||
- `src/`: Contains all core game logic and engine implementation.
|
||||
- `third_party/`: Vendored dependencies (zlib, SDL2, etc.).
|
||||
- `os/`: Platform-specific assets and configuration (icons, plists, etc.).
|
||||
- `cmake/`: Custom CMake scripts.
|
||||
- `files/`: Default configuration files and data assets.
|
||||
- `sfall_testing/`: SSL scripts for testing Sfall-related functionality.
|
||||
|
||||
## Tool Usage
|
||||
- **Prioritize CLion MCP**: Always use the CLion MCP tools (e.g., `mcp_clion_search_symbols`, `mcp_clion_get_symbol_info`) for finding code definitions and symbols instead of performing manual text-based searches with `grep_search` or `glob`. Use manual text searches only when symbol-based search is not applicable (e.g., searching for non-code text or specific patterns).
|
||||
|
||||
## Development Workflow
|
||||
1. **Research**: Understand how a feature was implemented in the original engine or Sfall before attempting to port or fix it.
|
||||
2. **Strategy**: Maintain compatibility with original game data and savegames.
|
||||
3. **Execution**: Apply surgical changes to `src/`.
|
||||
4. **Validation**: Ensure changes compile across platforms if possible, or at least don't break the build for the current platform.
|
||||
@@ -32,10 +32,10 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Art and appearance | art_exists<br>refresh_pc_art<br>art_cache_clear<br>set_hero_race<br>set_hero_style | implemented: art_exists, refresh_pc_art, art_cache_clear | - |
|
||||
| Tiles and paths | get_tile_fid<br>tile_under_cursor<br>tile_light<br>tile_get_objs<br>tile_refresh_display<br>obj_blocking_tile<br>tile_by_position<br>get_tile_ground_fid<br>get_tile_roof_fid<br>obj_blocking_line<br>path_find_to<br>objects_in_radius | âś… except objects_in_radius | - |
|
||||
| Utility | sprintf<br>typeof<br>atoi<br>atof | âś… | - |
|
||||
| Utility / Strings | string_split<br>substr<br>strlen<br>charcode<br>get_string_pointer<br>string_find<br>string_find_from<br>string_format<br>string_format_array<br>string_replace<br>string_to_case<br>string_compare | âś… except get_string_pointer | `get_string_pointer` is deprecated and intentionally omitted. |
|
||||
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | implemented: all except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
|
||||
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | implemented: all except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values and there is no float getter yet. |
|
||||
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… except init_hook | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
|
||||
| Utility / Strings | string_split<br>substr<br>strlen<br>charcode<br>get_string_pointer<br>string_find<br>string_find_from<br>string_format<br>string_format_array<br>string_replace<br>string_to_case<br>string_compare | âś… | `get_string_pointer` is deprecated and intentionally omitted. |
|
||||
| Interface / Tags | show_iface_tag<br>hide_iface_tag<br>is_iface_tag_active<br>set_iface_tag_text<br>add_iface_tag | âś… except set_iface_tag_text, add_iface_tag | CE only handles built-in interface tags here; custom tag creation/text is not supported yet. |
|
||||
| Global variables | set_sfall_global<br>get_sfall_global_int<br>get_sfall_global_float | âś… except get_sfall_global_float | Current CE storage is int-backed; `set_sfall_global` stores integer values |
|
||||
| Hooks / Hook functions | init_hook<br>get_sfall_arg<br>get_sfall_args<br>get_sfall_arg_at<br>set_sfall_return<br>set_sfall_arg<br>register_hook<br>register_hook_proc<br>register_hook_proc_spec | âś… | See below for implemented hooks. `init_hook` is deprecated and will not be implemented. register_hook_proc and register_hook_proc_spec both add hooks to the *end* of the hook list, instead of beginning and end, respectively. |
|
||||
| Arrays / Array functions | create_array<br>temp_array<br>fix_array<br>get/set_array<br>resize_array<br>free_array<br>scan_array<br>len_array<br>save/load_array<br>array_key<br>arrayexpr | âś… except save_array, load_array | - |
|
||||
| Perks and traits / NPC perks | set_fake_perk_npc<br>set_fake_trait_npc<br>set_selectable_perk_npc<br>has_fake_perk_npc<br>has_fake_trait_npc | not implemented | - |
|
||||
| Global scripts / Global script functions | set_global_script_repeat<br>set_global_script_type<br>available_global_script_types | implemented: all except available_global_script_types | - |
|
||||
@@ -48,7 +48,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| Interface / Cursor | get/set_cursor_mode | âś… | - |
|
||||
| Locks | lock_is_jammed<br>unjam_lock<br>set_unjam_locks_time | not implemented | - |
|
||||
| INI settings | modified_ini<br>get_ini_setting<br>get_ini_string<br>get_ini_section<br>get_ini_sections<br>get_ini_config<br>get_ini_config_db<br>set_ini_setting | âś… except modified_ini, get_ini_config, get_ini_config_db | `modified_ini` is intentionally omitted as deprecated. |
|
||||
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>set_script<br>get_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get_object_data<br>set_object_data<br>get_flags<br>set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get_script, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - |
|
||||
| Objects and scripts | set_self<br>set_dude_obj<br>real_dude_obj<br>remove_script<br>set_script<br>get_script<br>obj_is_carrying_obj<br>loot_obj<br>dialog_obj<br>obj_under_cursor<br>get_object_data<br>set_object_data<br>get_flags<br>set_flags<br>set_unique_id<br>set_scr_name<br>obj_is_openable<br>get/set_proto_data<br>get_object_ai_data | implemented: set_self, get_script, obj_is_carrying_obj, loot_obj, dialog_obj, obj_under_cursor, get_object_data, get_flags, set_flags, get_proto_data, set_proto_data | - |
|
||||
| Other / Game management | set_movie_path<br>stop_game<br>resume_game<br>mark_movie_played<br>game_loaded<br>get_game_mode<br>get_uptime<br>signal_close_game | implemented: game_loaded, get_game_mode, get_uptime | - |
|
||||
| Other | input_funcs_available<br>get_year<br>set_dm_model<br>set_df_model<br>set_pipboy_available<br>get_kill_counter<br>mod_kill_counter<br>active_hand<br>toggle_active_hand<br>set_pickpocket_max<br>set_hit_chance_max<br>set_xp_mod<br>set_critter_hit_chance_mod<br>set_base_hit_chance_mod<br>inc_npc_level<br>get_npc_level<br>get/set_viewport_x/y<br>set_hp_per_level_mod<br>get/set_unspent_ap_bonus<br>get/set_unspent_ap_perk_bonus<br>hero_select_win<br>create_message_window<br>get_light_level<br>gdialog_get_barter_mod<br>set_inven_ap_cost<br>set_base_pickpocket_mod<br>set_critter_pickpocket_mod<br>message_str_game<br>sneak_success<br>create_spatial<br>unwield_slot<br>get_inven_ap_cost<br>add_g_timer_event<br>add_extra_msg_file<br>get_metarule_table<br>metarule_exist<br>npc_engine_level_up<br>remove_timer_event<br>set_drugs_data<br>spatial_radius | implemented: get_year, active_hand, toggle_active_hand, create_message_window, message_str_game, add_extra_msg_file, metarule_exist | `input_funcs_available`, `nb_create_char` are deprecated in sfall and intentionally absent in CE. `add_extra_msg_file` does not support the explicit `fileNumber` form in CE. |
|
||||
|
||||
@@ -79,7 +79,7 @@ See [`https://sfall-team.github.io/sfall/`](https://sfall-team.github.io/sfall/)
|
||||
| InventoryMove | `HOOK_INVENTORYMOVE` | âś… | - |
|
||||
| InvenWield | `HOOK_INVENWIELD` | đźš« | - |
|
||||
| AdjustFID | `HOOK_ADJUSTFID` | đźš« | - |
|
||||
| CombatTurn | `HOOK_COMBATTURN` | đźš« | - |
|
||||
| CombatTurn | `HOOK_COMBATTURN` | âś… | - |
|
||||
| StdProcedure | `HOOK_STDPROCEDURE` | đźš« | Et tu |
|
||||
| StdProcedureEnd | `HOOK_STDPROCEDURE_END` | đźš« | - |
|
||||
| CarTravel | `HOOK_CARTRAVEL` | đźš« | - |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
[main]
|
||||
RadiationEffectCount=8
|
||||
RadiationLevelCount=6
|
||||
RadiationEffectPrimaryStatCount=6
|
||||
RadiationThresholds=99,199,399,599,999
|
||||
RadiationEnduranceModifiers=2,0,-2,-4,-6,-8
|
||||
RadiationEffectStats=0,1,2,3,4,5,6,7
|
||||
|
||||
[RadiationEffectPenalties]
|
||||
Level0=0,0,0,0,0,0,0,0
|
||||
Level1=-1,0,0,0,0,0,0,0
|
||||
Level2=-1,0,0,0,0,-1,0,-3
|
||||
Level3=-2,0,-1,0,0,-2,-5,-5
|
||||
Level4=-4,-3,-3,-3,-1,-5,-15,-10
|
||||
Level5=-6,-5,-5,-5,-3,-6,-20,-10
|
||||
+100
-3
@@ -2,12 +2,97 @@ plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
def sdlJavaBindingsDir = file('src/main/java/org/libsdl/app')
|
||||
|
||||
def findFetchedSdlJavaBindingsDir = { String variantName ->
|
||||
String variantCap = variantName.capitalize()
|
||||
File intermediatesVariantDir = file("build/intermediates/cxx/${variantCap}")
|
||||
if (!intermediatesVariantDir.isDirectory()) {
|
||||
throw new GradleException("SDL sync check failed: missing active CXX intermediates under ${intermediatesVariantDir}. Run externalNativeBuild${variantCap} first.")
|
||||
}
|
||||
|
||||
File[] activeHashDirs = intermediatesVariantDir.listFiles()
|
||||
if (activeHashDirs == null) {
|
||||
throw new GradleException("SDL sync check failed: could not list active CXX intermediates under ${intermediatesVariantDir}.")
|
||||
}
|
||||
|
||||
activeHashDirs = activeHashDirs.findAll { it.isDirectory() }.sort { a, b -> a.name <=> b.name }
|
||||
if (activeHashDirs.size() != 1) {
|
||||
throw new GradleException(
|
||||
"SDL sync check failed: expected exactly one active CXX hash directory under ${intermediatesVariantDir}, found ${activeHashDirs*.name}."
|
||||
)
|
||||
}
|
||||
|
||||
File fetchedDir = file(".cxx/${variantCap}/${activeHashDirs[0].name}/arm64-v8a/_deps/sdl2-src/android-project/app/src/main/java/org/libsdl/app")
|
||||
|
||||
if (!fetchedDir.isDirectory()) {
|
||||
throw new GradleException("SDL sync check failed: could not locate fetched SDL Java bindings under ${fetchedDir}.")
|
||||
}
|
||||
|
||||
return fetchedDir
|
||||
}
|
||||
|
||||
def assertSdlJavaBindingsMatch = { String variantName ->
|
||||
File fetchedDir = findFetchedSdlJavaBindingsDir(variantName)
|
||||
|
||||
if (!sdlJavaBindingsDir.isDirectory()) {
|
||||
throw new GradleException("SDL sync check failed: committed SDL Java bindings directory is missing or unreadable: ${sdlJavaBindingsDir}.")
|
||||
}
|
||||
|
||||
File[] committedFiles = sdlJavaBindingsDir.listFiles()
|
||||
if (committedFiles == null) {
|
||||
throw new GradleException("SDL sync check failed: could not list committed SDL Java bindings directory: ${sdlJavaBindingsDir}.")
|
||||
}
|
||||
|
||||
File[] fetchedFiles = fetchedDir.listFiles()
|
||||
if (fetchedFiles == null) {
|
||||
throw new GradleException("SDL sync check failed: could not list fetched SDL Java bindings directory: ${fetchedDir}.")
|
||||
}
|
||||
|
||||
Set<String> committedNames = committedFiles
|
||||
.findAll { it.isFile() && it.name.endsWith('.java') }
|
||||
.collect { it.name }
|
||||
.toSet()
|
||||
Set<String> fetchedNames = fetchedFiles
|
||||
.findAll { it.isFile() && it.name.endsWith('.java') }
|
||||
.collect { it.name }
|
||||
.toSet()
|
||||
|
||||
Set<String> missingFromCommitted = fetchedNames - committedNames
|
||||
Set<String> extraInCommitted = committedNames - fetchedNames
|
||||
|
||||
if (!missingFromCommitted.isEmpty() || !extraInCommitted.isEmpty()) {
|
||||
throw new GradleException(
|
||||
"SDL Android Java bindings are out of sync with native SDL for ${variantName}.\n" +
|
||||
"Fetched SDL dir: ${fetchedDir}\n" +
|
||||
"Missing from committed bindings: ${missingFromCommitted.sort()}\n" +
|
||||
"Extra committed bindings: ${extraInCommitted.sort()}\n" +
|
||||
"Sync os/android/app/src/main/java/org/libsdl/app from the fetched SDL android-project sources."
|
||||
)
|
||||
}
|
||||
|
||||
List<String> changedFiles = committedNames.findAll { fileName ->
|
||||
File committedFile = new File(sdlJavaBindingsDir, fileName)
|
||||
File fetchedFile = new File(fetchedDir, fileName)
|
||||
committedFile.getText('UTF-8') != fetchedFile.getText('UTF-8')
|
||||
}.sort()
|
||||
|
||||
if (!changedFiles.isEmpty()) {
|
||||
throw new GradleException(
|
||||
"SDL Android Java bindings are out of sync with native SDL for ${variantName}.\n" +
|
||||
"Fetched SDL dir: ${fetchedDir}\n" +
|
||||
"Changed files: ${changedFiles}\n" +
|
||||
"Sync os/android/app/src/main/java/org/libsdl/app from the fetched SDL android-project sources."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.alexbatalov.fallout2ce'
|
||||
minSdk 21
|
||||
minSdk 24
|
||||
targetSdk 32
|
||||
versionCode 4
|
||||
versionName '1.3.0'
|
||||
@@ -78,8 +163,20 @@ android {
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
tasks["merge${variant.name.capitalize()}Assets"]
|
||||
.dependsOn("externalNativeBuild${variant.name.capitalize()}")
|
||||
String variantCap = variant.name.capitalize()
|
||||
String syncCheckTaskName = "checkSdlJavaBindingsSync${variantCap}"
|
||||
|
||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||
tasks.register(syncCheckTaskName) {
|
||||
dependsOn("externalNativeBuild${variantCap}")
|
||||
doLast {
|
||||
assertSdlJavaBindingsMatch(variant.name)
|
||||
}
|
||||
}
|
||||
|
||||
tasks["merge${variantCap}Assets"]
|
||||
.dependsOn(syncCheckTaskName)
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.hasProperty('EXCLUDE_NATIVE_LIBS')) {
|
||||
|
||||
@@ -13,23 +13,18 @@ import java.io.OutputStream;
|
||||
public class FileUtils {
|
||||
|
||||
static boolean copyRecursively(ContentResolver contentResolver, DocumentFile src, File dest) {
|
||||
if (dest == null || (!dest.exists() && !dest.mkdirs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final DocumentFile[] documentFiles = src.listFiles();
|
||||
for (final DocumentFile documentFile : documentFiles) {
|
||||
final String name = documentFile.getName();
|
||||
if (name == null || name.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (documentFile.isFile()) {
|
||||
if (!copyFile(contentResolver, documentFile, new File(dest, name))) {
|
||||
if (!copyFile(contentResolver, documentFile, new File(dest, documentFile.getName()))) {
|
||||
return false;
|
||||
}
|
||||
} else if (documentFile.isDirectory()) {
|
||||
final File subdirectory = new File(dest, name);
|
||||
final File subdirectory = new File(dest, documentFile.getName());
|
||||
if (!subdirectory.exists()) {
|
||||
subdirectory.mkdir();
|
||||
}
|
||||
|
||||
if (!copyRecursively(contentResolver, documentFile, subdirectory)) {
|
||||
return false;
|
||||
}
|
||||
@@ -39,22 +34,18 @@ public class FileUtils {
|
||||
}
|
||||
|
||||
private static boolean copyFile(ContentResolver contentResolver, DocumentFile src, File dest) {
|
||||
final File parent = dest.getParentFile();
|
||||
if (parent != null && !parent.exists() && !parent.mkdirs()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (final InputStream inputStream = contentResolver.openInputStream(src.getUri());
|
||||
final OutputStream outputStream = new FileOutputStream(dest)) {
|
||||
if (inputStream == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
final InputStream inputStream = contentResolver.openInputStream(src.getUri());
|
||||
final OutputStream outputStream = new FileOutputStream(dest);
|
||||
|
||||
final byte[] buffer = new byte[16384];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
|
||||
@@ -2,12 +2,10 @@ package com.alexbatalov.fallout2ce;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
@@ -19,18 +17,18 @@ public class ImportActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
launchImportPicker();
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
startActivityForResult(intent, IMPORT_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
||||
if (requestCode == IMPORT_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
final Uri treeUri = resultData != null ? resultData.getData() : null;
|
||||
final Uri treeUri = resultData.getData();
|
||||
if (treeUri != null) {
|
||||
grantImportPermissions(treeUri, resultData);
|
||||
final DocumentFile treeDocument = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (treeDocument != null && treeDocument.isDirectory()) {
|
||||
if (treeDocument != null) {
|
||||
copyFiles(treeDocument);
|
||||
return;
|
||||
}
|
||||
@@ -44,57 +42,18 @@ public class ImportActivity extends Activity {
|
||||
}
|
||||
|
||||
private void copyFiles(DocumentFile treeDocument) {
|
||||
final ProgressDialog dialog = createProgressDialog();
|
||||
ProgressDialog dialog = createProgressDialog();
|
||||
dialog.show();
|
||||
|
||||
new Thread(() -> {
|
||||
final ContentResolver contentResolver = getContentResolver();
|
||||
final File externalFilesDir = getExternalFilesDir(null);
|
||||
final boolean success = externalFilesDir != null
|
||||
&& FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
File externalFilesDir = getExternalFilesDir(null);
|
||||
FileUtils.copyRecursively(contentResolver, treeDocument, externalFilesDir);
|
||||
|
||||
runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
|
||||
if (success) {
|
||||
startMainActivity();
|
||||
} else {
|
||||
Toast.makeText(this, R.string.import_failed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
finish();
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void launchImportPicker() {
|
||||
final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, IMPORT_REQUEST_CODE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(this, R.string.import_picker_unavailable, Toast.LENGTH_LONG).show();
|
||||
startMainActivity();
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void grantImportPermissions(Uri treeUri, Intent resultData) {
|
||||
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
|
||||
if (resultData != null) {
|
||||
flags = resultData.getFlags()
|
||||
& (Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
}
|
||||
|
||||
try {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, flags);
|
||||
} catch (SecurityException ignored) {
|
||||
// Some providers do not offer persistable grants. One-shot access is enough for import.
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void startMainActivity() {
|
||||
|
||||
@@ -186,7 +186,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
@@ -429,7 +429,7 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (newState == 0) {
|
||||
mIsConnected = false;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ public class HIDDeviceManager {
|
||||
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
|
||||
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
|
||||
|
||||
// Get endpoint details
|
||||
// Get endpoint details
|
||||
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
|
||||
{
|
||||
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
|
||||
@@ -251,6 +251,8 @@ public class HIDDeviceManager {
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2c22, // Qanba
|
||||
0x2dc8, // 8BitDo
|
||||
0x9886, // ASTRO Gaming
|
||||
};
|
||||
|
||||
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC &&
|
||||
@@ -271,15 +273,20 @@ public class HIDDeviceManager {
|
||||
final int XB1_IFACE_SUBCLASS = 71;
|
||||
final int XB1_IFACE_PROTOCOL = 208;
|
||||
final int[] SUPPORTED_VENDORS = {
|
||||
0x03f0, // HP
|
||||
0x044f, // Thrustmaster
|
||||
0x045e, // Microsoft
|
||||
0x0738, // Mad Catz
|
||||
0x0b05, // ASUS
|
||||
0x0e6f, // PDP
|
||||
0x0f0d, // Hori
|
||||
0x10f5, // Turtle Beach
|
||||
0x1532, // Razer Wildcat
|
||||
0x20d6, // PowerA
|
||||
0x24c6, // PowerA
|
||||
0x2dc8, /* 8BitDo */
|
||||
0x2dc8, // 8BitDo
|
||||
0x2e24, // Hyperkin
|
||||
0x3537, // GameSir
|
||||
};
|
||||
|
||||
if (usbInterface.getId() == 0 &&
|
||||
@@ -353,13 +360,19 @@ public class HIDDeviceManager {
|
||||
private void initializeBluetooth() {
|
||||
Log.d(TAG, "Initializing Bluetooth");
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 &&
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12 */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH_CONNECT");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 30 /* Android 11.0 (R) */ &&
|
||||
mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18)) {
|
||||
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) || (Build.VERSION.SDK_INT < 18 /* Android 4.3 (JELLY_BEAN_MR2) */)) {
|
||||
Log.d(TAG, "Couldn't initialize Bluetooth, this version of Android does not support Bluetooth LE");
|
||||
return;
|
||||
}
|
||||
@@ -524,7 +537,7 @@ public class HIDDeviceManager {
|
||||
for (HIDDevice device : mDevicesById.values()) {
|
||||
device.setFrozen(frozen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -573,12 +586,18 @@ public class HIDDeviceManager {
|
||||
try {
|
||||
final int FLAG_MUTABLE = 0x02000000; // PendingIntent.FLAG_MUTABLE, but don't require SDK 31
|
||||
int flags;
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
if (Build.VERSION.SDK_INT >= 31 /* Android 12.0 (S) */) {
|
||||
flags = FLAG_MUTABLE;
|
||||
} else {
|
||||
flags = 0;
|
||||
}
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
if (Build.VERSION.SDK_INT >= 33 /* Android 14.0 (U) */) {
|
||||
Intent intent = new Intent(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, intent, flags));
|
||||
} else {
|
||||
mUsbManager.requestPermission(usbDevice, PendingIntent.getBroadcast(mContext, 0, new Intent(HIDDeviceManager.ACTION_USB_PERMISSION), flags));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.v(TAG, "Couldn't request permission for USB device " + usbDevice);
|
||||
HIDDeviceOpenResult(deviceID, false);
|
||||
|
||||
@@ -52,7 +52,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getSerialNumber() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
try {
|
||||
result = mDevice.getSerialNumber();
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getManufacturerName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getManufacturerName();
|
||||
}
|
||||
if (result == null) {
|
||||
@@ -86,7 +86,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
@Override
|
||||
public String getProductName() {
|
||||
String result = null;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
result = mDevice.getProductName();
|
||||
}
|
||||
if (result == null) {
|
||||
|
||||
@@ -29,6 +29,7 @@ public class SDL {
|
||||
|
||||
// This function stores the current activity (SDL or not)
|
||||
public static void setContext(Context context) {
|
||||
SDLAudioManager.setContext(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@@ -37,6 +38,10 @@ public class SDL {
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
loadLibrary(libraryName, mContext);
|
||||
}
|
||||
|
||||
public static void loadLibrary(String libraryName, Context context) throws UnsatisfiedLinkError, SecurityException, NullPointerException {
|
||||
|
||||
if (libraryName == null) {
|
||||
throw new NullPointerException("No library name provided.");
|
||||
@@ -52,10 +57,10 @@ public class SDL {
|
||||
// To use ReLinker, just add it as a dependency. For more information, see
|
||||
// https://github.com/KeepSafe/ReLinker for ReLinker's repository.
|
||||
//
|
||||
Class<?> relinkClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = mContext.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = mContext.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = mContext.getClassLoader().loadClass("java.lang.String");
|
||||
Class<?> relinkClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker");
|
||||
Class<?> relinkListenerClass = context.getClassLoader().loadClass("com.getkeepsafe.relinker.ReLinker$LoadListener");
|
||||
Class<?> contextClass = context.getClassLoader().loadClass("android.content.Context");
|
||||
Class<?> stringClass = context.getClassLoader().loadClass("java.lang.String");
|
||||
|
||||
// Get a 'force' instance of the ReLinker, so we can ensure libraries are reinstalled if
|
||||
// they've changed during updates.
|
||||
@@ -65,7 +70,7 @@ public class SDL {
|
||||
|
||||
// Actually load the library!
|
||||
Method loadMethod = relinkInstanceClass.getDeclaredMethod("loadLibrary", contextClass, stringClass, stringClass, relinkListenerClass);
|
||||
loadMethod.invoke(relinkInstance, mContext, libraryName, null, null);
|
||||
loadMethod.invoke(relinkInstance, context, libraryName, null, null);
|
||||
}
|
||||
catch (final Throwable e) {
|
||||
// Fall back
|
||||
|
||||
@@ -60,8 +60,8 @@ import java.util.Locale;
|
||||
public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {
|
||||
private static final String TAG = "SDL";
|
||||
private static final int SDL_MAJOR_VERSION = 2;
|
||||
private static final int SDL_MINOR_VERSION = 26;
|
||||
private static final int SDL_MICRO_VERSION = 1;
|
||||
private static final int SDL_MINOR_VERSION = 32;
|
||||
private static final int SDL_MICRO_VERSION = 8;
|
||||
/*
|
||||
// Display InputType.SOURCE/CLASS of events and devices
|
||||
//
|
||||
@@ -89,11 +89,11 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
| InputDevice.SOURCE_CLASS_POSITION
|
||||
| InputDevice.SOURCE_CLASS_TRACKBALL);
|
||||
|
||||
if (s2 != 0) cls += "Some_Unkown";
|
||||
if (s2 != 0) cls += "Some_Unknown";
|
||||
|
||||
s2 = s_copy & InputDevice.SOURCE_ANY; // keep source only, no class;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
tst = InputDevice.SOURCE_BLUETOOTH_STYLUS;
|
||||
if ((s & tst) == tst) src += " BLUETOOTH_STYLUS";
|
||||
s2 &= ~tst;
|
||||
@@ -107,7 +107,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
if ((s & tst) == tst) src += " GAMEPAD";
|
||||
s2 &= ~tst;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
tst = InputDevice.SOURCE_HDMI;
|
||||
if ((s & tst) == tst) src += " HDMI";
|
||||
s2 &= ~tst;
|
||||
@@ -146,7 +146,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
if ((s & tst) == tst) src += " TOUCHSCREEN";
|
||||
s2 &= ~tst;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
tst = InputDevice.SOURCE_TOUCH_NAVIGATION;
|
||||
if ((s & tst) == tst) src += " TOUCH_NAVIGATION";
|
||||
s2 &= ~tst;
|
||||
@@ -163,14 +163,14 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
if (s == FLAG_TAINTED) src += " FLAG_TAINTED";
|
||||
s2 &= ~FLAG_TAINTED;
|
||||
|
||||
if (s2 != 0) src += " Some_Unkown";
|
||||
if (s2 != 0) src += " Some_Unknown";
|
||||
|
||||
Log.v(TAG, prefix + "int=" + s_copy + " CLASS={" + cls + " } source(s):" + src);
|
||||
}
|
||||
*/
|
||||
|
||||
public static boolean mIsResumedCalled, mHasFocus;
|
||||
public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24);
|
||||
public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */);
|
||||
|
||||
// Cursor types
|
||||
// private static final int SDL_SYSTEM_CURSOR_NONE = -1;
|
||||
@@ -224,9 +224,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
|
||||
protected static SDLGenericMotionListener_API12 getMotionListener() {
|
||||
if (mMotionListener == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mMotionListener = new SDLGenericMotionListener_API26();
|
||||
} else if (Build.VERSION.SDK_INT >= 24) {
|
||||
} else if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
mMotionListener = new SDLGenericMotionListener_API24();
|
||||
} else {
|
||||
mMotionListener = new SDLGenericMotionListener_API12();
|
||||
@@ -281,7 +281,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
// Load the .so
|
||||
public void loadLibraries() {
|
||||
for (String lib : getLibraries()) {
|
||||
SDL.loadLibrary(lib);
|
||||
SDL.loadLibrary(lib, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
mHIDDeviceManager = HIDDeviceManager.acquire(this);
|
||||
|
||||
// Set up the surface
|
||||
mSurface = createSDLSurface(getApplication());
|
||||
mSurface = createSDLSurface(this);
|
||||
|
||||
mLayout = new RelativeLayout(this);
|
||||
mLayout.addView(mSurface);
|
||||
@@ -404,7 +404,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
SDLActivity.onNativeOrientationChanged(mCurrentOrientation);
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {
|
||||
mCurrentLocale = getContext().getResources().getConfiguration().locale;
|
||||
} else {
|
||||
mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0);
|
||||
@@ -588,6 +588,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
mHIDDeviceManager = null;
|
||||
}
|
||||
|
||||
SDLAudioManager.release(this);
|
||||
|
||||
if (SDLActivity.mBrokenLibraries) {
|
||||
super.onDestroy();
|
||||
return;
|
||||
@@ -766,7 +768,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
break;
|
||||
case COMMAND_CHANGE_WINDOW_STYLE:
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
if (context instanceof Activity) {
|
||||
Window window = ((Activity) context).getWindow();
|
||||
if (window != null) {
|
||||
@@ -788,6 +790,9 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
SDLActivity.mFullscreenModeActive = false;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 28 /* Android 9 (Pie) */) {
|
||||
window.getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "error handling message, getContext() returned no Activity");
|
||||
@@ -841,7 +846,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
msg.obj = data;
|
||||
boolean result = commandHandler.sendMessage(msg);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
if (command == COMMAND_CHANGE_WINDOW_STYLE) {
|
||||
// Ensure we don't return until the resize has actually happened,
|
||||
// or 500ms have passed.
|
||||
@@ -969,15 +974,18 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
/* If set, hint "explicitly controls which UI orientations are allowed". */
|
||||
if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
|
||||
orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
} else if (hint.contains("LandscapeRight")) {
|
||||
orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
} else if (hint.contains("LandscapeLeft")) {
|
||||
orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
} else if (hint.contains("LandscapeRight")) {
|
||||
orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
}
|
||||
|
||||
if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
|
||||
/* exact match to 'Portrait' to distinguish with PortraitUpsideDown */
|
||||
boolean contains_Portrait = hint.contains("Portrait ") || hint.endsWith("Portrait");
|
||||
|
||||
if (contains_Portrait && hint.contains("PortraitUpsideDown")) {
|
||||
orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
|
||||
} else if (hint.contains("Portrait")) {
|
||||
} else if (contains_Portrait) {
|
||||
orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
||||
} else if (hint.contains("PortraitUpsideDown")) {
|
||||
orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
||||
@@ -990,8 +998,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
/* No valid hint, nothing is explicitly allowed */
|
||||
if (!is_portrait_allowed && !is_landscape_allowed) {
|
||||
if (resizable) {
|
||||
/* All orientations are allowed */
|
||||
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
||||
/* All orientations are allowed, respecting user orientation lock setting */
|
||||
req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
|
||||
} else {
|
||||
/* Fixed window and nothing specified. Get orientation from w/h of created window */
|
||||
req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
|
||||
@@ -1000,8 +1008,8 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
/* At least one orientation is allowed */
|
||||
if (resizable) {
|
||||
if (is_portrait_allowed && is_landscape_allowed) {
|
||||
/* hint allows both landscape and portrait, promote to full sensor */
|
||||
req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
|
||||
/* hint allows both landscape and portrait, promote to full user */
|
||||
req = ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
|
||||
} else {
|
||||
/* Use the only one allowed "orientation" */
|
||||
req = (is_landscape_allowed ? orientation_landscape : orientation_portrait);
|
||||
@@ -1090,7 +1098,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
// thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result,
|
||||
// we should stick to relative mode.
|
||||
//
|
||||
if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) {
|
||||
if (Build.VERSION.SDK_INT < 27 /* Android 8.1 (O_MR1) */ && isDeXMode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1180,7 +1188,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static boolean isDeXMode() {
|
||||
if (Build.VERSION.SDK_INT < 24) {
|
||||
if (Build.VERSION.SDK_INT < 24 /* Android 7.0 (N) */) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
@@ -1340,23 +1348,6 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
}
|
||||
|
||||
if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (isTextInputEvent(event)) {
|
||||
if (ic != null) {
|
||||
ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
|
||||
} else {
|
||||
SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
|
||||
}
|
||||
}
|
||||
onNativeKeyDown(keyCode);
|
||||
return true;
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
onNativeKeyUp(keyCode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((source & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
|
||||
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
|
||||
// they are ignored here because sending them as mouse input to SDL is messy
|
||||
@@ -1371,6 +1362,21 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
}
|
||||
}
|
||||
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
if (isTextInputEvent(event)) {
|
||||
if (ic != null) {
|
||||
ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
|
||||
} else {
|
||||
SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
|
||||
}
|
||||
}
|
||||
onNativeKeyDown(keyCode);
|
||||
return true;
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP) {
|
||||
onNativeKeyUp(keyCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1617,7 +1623,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
private final Runnable rehideSystemUi = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||
@@ -1670,7 +1676,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
|
||||
++mLastCursorID;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
try {
|
||||
mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY));
|
||||
} catch (Exception e) {
|
||||
@@ -1686,7 +1692,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void destroyCustomCursor(int cursorID) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
try {
|
||||
mCursors.remove(cursorID);
|
||||
} catch (Exception e) {
|
||||
@@ -1700,7 +1706,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
*/
|
||||
public static boolean setCustomCursor(int cursorID) {
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
try {
|
||||
mSurface.setPointerIcon(mCursors.get(cursorID));
|
||||
} catch (Exception e) {
|
||||
@@ -1755,7 +1761,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
cursor_type = 1002; //PointerIcon.TYPE_HAND;
|
||||
break;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
try {
|
||||
mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type));
|
||||
} catch (Exception e) {
|
||||
@@ -1769,7 +1775,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static void requestPermission(String permission, int requestCode) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
nativePermissionResult(requestCode, true);
|
||||
return;
|
||||
}
|
||||
@@ -1798,7 +1804,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
|
||||
i.setData(Uri.parse(url));
|
||||
|
||||
int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
flags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
|
||||
} else {
|
||||
flags |= Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET;
|
||||
@@ -2002,6 +2008,18 @@ class SDLInputConnection extends BaseInputConnection {
|
||||
|
||||
@Override
|
||||
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
||||
if (Build.VERSION.SDK_INT <= 29 /* Android 10.0 (Q) */) {
|
||||
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions>/14560344/android-backspace-in-webview-baseinputconnection
|
||||
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
|
||||
if (beforeLength > 0 && afterLength == 0) {
|
||||
// backspace(s)
|
||||
while (beforeLength-- > 0) {
|
||||
nativeGenerateScancodeForUnichar('\b');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!super.deleteSurroundingText(beforeLength, afterLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.libsdl.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceCallback;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioRecord;
|
||||
@@ -8,34 +11,67 @@ import android.media.MediaRecorder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
public class SDLAudioManager
|
||||
{
|
||||
import java.util.Arrays;
|
||||
|
||||
public class SDLAudioManager {
|
||||
protected static final String TAG = "SDLAudio";
|
||||
|
||||
protected static AudioTrack mAudioTrack;
|
||||
protected static AudioRecord mAudioRecord;
|
||||
protected static Context mContext;
|
||||
|
||||
private static final int[] NO_DEVICES = {};
|
||||
|
||||
private static AudioDeviceCallback mAudioDeviceCallback;
|
||||
|
||||
public static void initialize() {
|
||||
mAudioTrack = null;
|
||||
mAudioRecord = null;
|
||||
mAudioDeviceCallback = null;
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */)
|
||||
{
|
||||
mAudioDeviceCallback = new AudioDeviceCallback() {
|
||||
@Override
|
||||
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
|
||||
Arrays.stream(addedDevices).forEach(deviceInfo -> addAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
|
||||
Arrays.stream(removedDevices).forEach(deviceInfo -> removeAudioDevice(deviceInfo.isSink(), deviceInfo.getId()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void setContext(Context context) {
|
||||
mContext = context;
|
||||
if (context != null) {
|
||||
registerAudioDeviceCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public static void release(Context context) {
|
||||
unregisterAudioDeviceCallback(context);
|
||||
}
|
||||
|
||||
// Audio
|
||||
|
||||
protected static String getAudioFormatString(int audioFormat) {
|
||||
switch (audioFormat) {
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
case AudioFormat.ENCODING_PCM_8BIT:
|
||||
return "8-bit";
|
||||
case AudioFormat.ENCODING_PCM_16BIT:
|
||||
return "16-bit";
|
||||
case AudioFormat.ENCODING_PCM_FLOAT:
|
||||
return "float";
|
||||
default:
|
||||
return Integer.toString(audioFormat);
|
||||
}
|
||||
}
|
||||
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
protected static int[] open(boolean isCapture, int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
int channelConfig;
|
||||
int sampleSize;
|
||||
int frameSize;
|
||||
@@ -43,14 +79,14 @@ public class SDLAudioManager
|
||||
Log.v(TAG, "Opening " + (isCapture ? "capture" : "playback") + ", requested " + desiredFrames + " frames of " + desiredChannels + " channel " + getAudioFormatString(audioFormat) + " audio at " + sampleRate + " Hz");
|
||||
|
||||
/* On older devices let's use known good settings */
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
if (Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
if (desiredChannels > 2) {
|
||||
desiredChannels = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack has sample rate limitation of 48000 (fixed in 5.0.2) */
|
||||
if (Build.VERSION.SDK_INT < 22) {
|
||||
if (Build.VERSION.SDK_INT < 22 /* Android 5.1 (LOLLIPOP_MR1) */) {
|
||||
if (sampleRate < 8000) {
|
||||
sampleRate = 8000;
|
||||
} else if (sampleRate > 48000) {
|
||||
@@ -59,7 +95,7 @@ public class SDLAudioManager
|
||||
}
|
||||
|
||||
if (audioFormat == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
int minSDKVersion = (isCapture ? 23 : 21);
|
||||
int minSDKVersion = (isCapture ? 23 /* Android 6.0 (M) */ : 21 /* Android 5.0 (LOLLIPOP) */);
|
||||
if (Build.VERSION.SDK_INT < minSDKVersion) {
|
||||
audioFormat = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
@@ -120,7 +156,7 @@ public class SDLAudioManager
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||
break;
|
||||
case 8:
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
channelConfig = AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||
} else {
|
||||
Log.v(TAG, "Requested " + desiredChannels + " channels, getting 5.1 surround");
|
||||
@@ -201,6 +237,10 @@ public class SDLAudioManager
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioRecord.setPreferredDevice(getOutputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioRecord.startRecording();
|
||||
}
|
||||
|
||||
@@ -224,6 +264,10 @@ public class SDLAudioManager
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */ && deviceId != 0) {
|
||||
mAudioTrack.setPreferredDevice(getInputAudioDeviceInfo(deviceId));
|
||||
}
|
||||
|
||||
mAudioTrack.play();
|
||||
}
|
||||
|
||||
@@ -238,11 +282,73 @@ public class SDLAudioManager
|
||||
return results;
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getInputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static AudioDeviceInfo getOutputAudioDeviceInfo(int deviceId) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS))
|
||||
.filter(deviceInfo -> deviceInfo.getId() == deviceId)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerAudioDeviceCallback() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.registerAudioDeviceCallback(mAudioDeviceCallback, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void unregisterAudioDeviceCallback(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.unregisterAudioDeviceCallback(mAudioDeviceCallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
public static int[] getAudioOutputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] getAudioInputDevices() {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
return Arrays.stream(audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).mapToInt(AudioDeviceInfo::getId).toArray();
|
||||
} else {
|
||||
return NO_DEVICES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] audioOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(false, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,6 +360,11 @@ public class SDLAudioManager
|
||||
return;
|
||||
}
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < 21 /* Android 5.0 (LOLLIPOP) */) {
|
||||
Log.e(TAG, "Attempted to make an incompatible audio call with uninitialized audio! (floating-point output is supported since Android 5.0 Lollipop)");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < buffer.length;) {
|
||||
int result = mAudioTrack.write(buffer, i, buffer.length - i, AudioTrack.WRITE_BLOCKING);
|
||||
if (result > 0) {
|
||||
@@ -326,18 +437,22 @@ public class SDLAudioManager
|
||||
/**
|
||||
* This method is called by SDL using JNI.
|
||||
*/
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames);
|
||||
public static int[] captureOpen(int sampleRate, int audioFormat, int desiredChannels, int desiredFrames, int deviceId) {
|
||||
return open(true, sampleRate, audioFormat, desiredChannels, desiredFrames, deviceId);
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadFloatBuffer(float[] buffer, boolean blocking) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return 0;
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
}
|
||||
}
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadShortBuffer(short[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
@@ -346,7 +461,7 @@ public class SDLAudioManager
|
||||
|
||||
/** This method is called by SDL using JNI. */
|
||||
public static int captureReadByteBuffer(byte[] buffer, boolean blocking) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
if (Build.VERSION.SDK_INT < 23 /* Android 6.0 (M) */) {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length);
|
||||
} else {
|
||||
return mAudioRecord.read(buffer, 0, buffer.length, blocking ? AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
|
||||
@@ -391,4 +506,9 @@ public class SDLAudioManager
|
||||
}
|
||||
|
||||
public static native int nativeSetupJNI();
|
||||
|
||||
public static native void removeAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
public static native void addAudioDevice(boolean isCapture, int deviceId);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class SDLControllerManager
|
||||
public static native int nativeAddJoystick(int device_id, String name, String desc,
|
||||
int vendor_id, int product_id,
|
||||
boolean is_accelerometer, int button_mask,
|
||||
int naxes, int nhats, int nballs);
|
||||
int naxes, int axis_mask, int nhats, int nballs);
|
||||
public static native int nativeRemoveJoystick(int device_id);
|
||||
public static native int nativeAddHaptic(int device_id, String name);
|
||||
public static native int nativeRemoveHaptic(int device_id);
|
||||
@@ -42,7 +42,7 @@ public class SDLControllerManager
|
||||
|
||||
public static void initialize() {
|
||||
if (mJoystickHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
if (Build.VERSION.SDK_INT >= 19 /* Android 4.4 (KITKAT) */) {
|
||||
mJoystickHandler = new SDLJoystickHandler_API19();
|
||||
} else {
|
||||
mJoystickHandler = new SDLJoystickHandler_API16();
|
||||
@@ -50,7 +50,7 @@ public class SDLControllerManager
|
||||
}
|
||||
|
||||
if (mHapticHandler == null) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
if (Build.VERSION.SDK_INT >= 26 /* Android 8.0 (O) */) {
|
||||
mHapticHandler = new SDLHapticHandler_API26();
|
||||
} else {
|
||||
mHapticHandler = new SDLHapticHandler();
|
||||
@@ -168,6 +168,32 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
arg1Axis = MotionEvent.AXIS_GAS;
|
||||
}
|
||||
|
||||
// Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
|
||||
// This is because the usual pairing are:
|
||||
// - AXIS_X + AXIS_Y (left stick).
|
||||
// - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
|
||||
// - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
|
||||
// This sorts the axes in the above order, which tends to be correct
|
||||
// for Xbox-ish game pads that have the right stick on RX/RY and the
|
||||
// triggers on Z/RZ.
|
||||
//
|
||||
// Gamepads that don't have AXIS_Z/AXIS_RZ but use
|
||||
// AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
|
||||
// - https://www.kernel.org/doc/html/latest/input/gamepad.html
|
||||
if (arg0Axis == MotionEvent.AXIS_Z) {
|
||||
arg0Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg0Axis;
|
||||
}
|
||||
if (arg1Axis == MotionEvent.AXIS_Z) {
|
||||
arg1Axis = MotionEvent.AXIS_RZ - 1;
|
||||
} else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
|
||||
--arg1Axis;
|
||||
}
|
||||
|
||||
return arg0Axis - arg1Axis;
|
||||
}
|
||||
}
|
||||
@@ -210,7 +236,7 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
mJoysticks.add(joystick);
|
||||
SDLControllerManager.nativeAddJoystick(joystick.device_id, joystick.name, joystick.desc,
|
||||
getVendorId(joystickDevice), getProductId(joystickDevice), false,
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), joystick.hats.size()/2, 0);
|
||||
getButtonMask(joystickDevice), joystick.axes.size(), getAxisMask(joystick.axes), joystick.hats.size()/2, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,6 +317,9 @@ class SDLJoystickHandler_API16 extends SDLJoystickHandler {
|
||||
public int getVendorId(InputDevice joystickDevice) {
|
||||
return 0;
|
||||
}
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
return -1;
|
||||
}
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
return -1;
|
||||
}
|
||||
@@ -308,6 +337,43 @@ class SDLJoystickHandler_API19 extends SDLJoystickHandler_API16 {
|
||||
return joystickDevice.getVendorId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAxisMask(List<InputDevice.MotionRange> ranges) {
|
||||
// For compatibility, keep computing the axis mask like before,
|
||||
// only really distinguishing 2, 4 and 6 axes.
|
||||
int axis_mask = 0;
|
||||
if (ranges.size() >= 2) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFTX) | (1 << SDL_GAMEPAD_AXIS_LEFTY))
|
||||
axis_mask |= 0x0003;
|
||||
}
|
||||
if (ranges.size() >= 4) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_RIGHTX) | (1 << SDL_GAMEPAD_AXIS_RIGHTY))
|
||||
axis_mask |= 0x000c;
|
||||
}
|
||||
if (ranges.size() >= 6) {
|
||||
// ((1 << SDL_GAMEPAD_AXIS_LEFT_TRIGGER) | (1 << SDL_GAMEPAD_AXIS_RIGHT_TRIGGER))
|
||||
axis_mask |= 0x0030;
|
||||
}
|
||||
// Also add an indicator bit for whether the sorting order has changed.
|
||||
// This serves to disable outdated gamecontrollerdb.txt mappings.
|
||||
boolean have_z = false;
|
||||
boolean have_past_z_before_rz = false;
|
||||
for (InputDevice.MotionRange range : ranges) {
|
||||
int axis = range.getAxis();
|
||||
if (axis == MotionEvent.AXIS_Z) {
|
||||
have_z = true;
|
||||
} else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
|
||||
have_past_z_before_rz = true;
|
||||
}
|
||||
}
|
||||
if (have_z && have_past_z_before_rz) {
|
||||
// If both these exist, the compare() function changed sorting order.
|
||||
// Set a bit to indicate this fact.
|
||||
axis_mask |= 0x8000;
|
||||
}
|
||||
return axis_mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonMask(InputDevice joystickDevice) {
|
||||
int button_mask = 0;
|
||||
@@ -480,13 +546,15 @@ class SDLHapticHandler {
|
||||
if (haptic == null) {
|
||||
InputDevice device = InputDevice.getDevice(deviceIds[i]);
|
||||
Vibrator vib = device.getVibrator();
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
if (vib != null) {
|
||||
if (vib.hasVibrator()) {
|
||||
haptic = new SDLHaptic();
|
||||
haptic.device_id = deviceIds[i];
|
||||
haptic.name = device.getName();
|
||||
haptic.vib = vib;
|
||||
mHaptics.add(haptic);
|
||||
SDLControllerManager.nativeAddHaptic(haptic.device_id, haptic.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -743,7 +811,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
|
||||
@Override
|
||||
public boolean supportsRelativeMouse() {
|
||||
return (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27));
|
||||
return (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -753,7 +821,7 @@ class SDLGenericMotionListener_API26 extends SDLGenericMotionListener_API24 {
|
||||
|
||||
@Override
|
||||
public boolean setRelativeMouseEnabled(boolean enabled) {
|
||||
if (!SDLActivity.isDeXMode() || (Build.VERSION.SDK_INT >= 27)) {
|
||||
if (!SDLActivity.isDeXMode() || Build.VERSION.SDK_INT >= 27 /* Android 8.1 (O_MR1) */) {
|
||||
if (enabled) {
|
||||
SDLActivity.getContentView().requestPointerCapture();
|
||||
} else {
|
||||
|
||||
@@ -116,7 +116,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
int nDeviceHeight = height;
|
||||
try
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) {
|
||||
DisplayMetrics realMetrics = new DisplayMetrics();
|
||||
mDisplay.getRealMetrics( realMetrics );
|
||||
nDeviceWidth = realMetrics.widthPixels;
|
||||
@@ -163,7 +163,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
// Don't skip in MultiWindow.
|
||||
if (skip) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) {
|
||||
if (SDLActivity.mSingleton.isInMultiWindowMode()) {
|
||||
Log.v("SDL", "Don't skip in Multi-Window");
|
||||
skip = false;
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
<string name="app_name">Fallout 2</string>
|
||||
<string name="loading_dialog_title">PLEASE STAND BY</string>
|
||||
<string name="loading_dialog_message">Copying files…</string>
|
||||
<string name="import_failed">Failed to import game files. Please pick a valid Fallout 2 folder and try again.</string>
|
||||
<string name="import_picker_unavailable">No compatible folder picker is available on this device.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "lib.arrays.h"
|
||||
#include "test_utils.h"
|
||||
// #include "define_lite.h"
|
||||
#include "define_lite.h"
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
procedure array_test_suite begin
|
||||
variable arr, i, arr2, map, k, v, s;
|
||||
variable critter, critter_count;
|
||||
test_suite_errors := 0;
|
||||
|
||||
display_msg("Testing arrays_equal functions...");
|
||||
@@ -170,6 +171,18 @@ procedure array_test_suite begin
|
||||
arr := [["one", "two"], {"three": ["four", -1]}];
|
||||
call assertEquals("nested 2", arr[1].three[0], "four");
|
||||
|
||||
display_msg("Testing LIST_CRITTERS validity...");
|
||||
critter_count := 0;
|
||||
foreach critter in list_as_array(LIST_CRITTERS) begin
|
||||
critter_count += 1;
|
||||
if (not critter) then begin
|
||||
call assertTrue("LIST_CRITTERS entry is not null", false);
|
||||
end else begin
|
||||
call assertNotEquals("LIST_CRITTERS entry pid is valid", obj_pid(critter), -1);
|
||||
end
|
||||
end
|
||||
call assertTrue("LIST_CRITTERS returns at least one entry", critter_count > 0);
|
||||
|
||||
display_msg("All tests finished with "+test_suite_errors+" errors.");
|
||||
|
||||
call report_test_results("arrays");
|
||||
@@ -184,4 +197,4 @@ end
|
||||
procedure start begin
|
||||
display_msg("Starting tests");
|
||||
call array_test_suite();
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "define_lite.h"
|
||||
#include "sfall.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#define TEST_ITEM_PID 40
|
||||
|
||||
procedure start begin
|
||||
variable item_a;
|
||||
variable item_b;
|
||||
variable item_c;
|
||||
variable pid_total;
|
||||
|
||||
display_msg("Testing inventory functions...");
|
||||
|
||||
item_a := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
|
||||
item_b := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
|
||||
item_c := create_object_sid(TEST_ITEM_PID, 0, 0, -1);
|
||||
|
||||
add_obj_to_inven(dude_obj, item_a);
|
||||
call assertEquals("single carried object count", obj_is_carrying_obj(dude_obj, item_a), 1);
|
||||
add_obj_to_inven(dude_obj, item_b);
|
||||
|
||||
call assertEquals("merged stack no longer matches original object pointer", obj_is_carrying_obj(dude_obj, item_a), 0);
|
||||
call assertEquals("merged stack matches replacement object pointer", obj_is_carrying_obj(dude_obj, item_b), 2);
|
||||
|
||||
pid_total := obj_is_carrying_obj_pid(dude_obj, TEST_ITEM_PID);
|
||||
call assertTrue("pid total covers both stacks", pid_total >= 2);
|
||||
call assertEquals("pid total matches merged carried stack quantity", pid_total, obj_is_carrying_obj(dude_obj, item_b));
|
||||
|
||||
call assertEquals("object not in inventory returns zero", obj_is_carrying_obj(dude_obj, item_c), 0);
|
||||
call assertEquals("null inventory object returns zero", obj_is_carrying_obj(0, item_a), 0);
|
||||
call assertEquals("null item object returns zero", obj_is_carrying_obj(dude_obj, 0), 0);
|
||||
|
||||
call assertEquals("remove merged stack quantity", rm_mult_objs_from_inven(dude_obj, item_b, 2), 2);
|
||||
destroy_object(item_c);
|
||||
|
||||
call report_test_results("inventory");
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user