diff --git a/Common/Thread/ThreadManager.cpp b/Common/Thread/ThreadManager.cpp index 8ac9ac0a22..4177b9f44b 100644 --- a/Common/Thread/ThreadManager.cpp +++ b/Common/Thread/ThreadManager.cpp @@ -98,7 +98,7 @@ static void WorkerThreadFunc(GlobalThreadContext *global, ThreadContext *thread) } void ThreadManager::Init(int numRealCores, int numLogicalCoresPerCpu) { - if (!global_->threads_.empty()) { + if (IsInitialized()) { Teardown(); } @@ -118,6 +118,8 @@ void ThreadManager::Init(int numRealCores, int numLogicalCoresPerCpu) { } void ThreadManager::EnqueueTask(Task *task, TaskType taskType) { + _assert_msg_(IsInitialized(), "ThreadManager not initialized"); + int maxThread; int threadOffset = 0; if (taskType == TaskType::CPU_COMPUTE) { @@ -158,7 +160,7 @@ void ThreadManager::EnqueueTask(Task *task, TaskType taskType) { } void ThreadManager::EnqueueTaskOnThread(int threadNum, Task *task, TaskType taskType) { - _assert_(threadNum >= 0 && threadNum < (int)global_->threads_.size()); + _assert_msg_(threadNum >= 0 && threadNum < (int)global_->threads_.size(), "Bad threadnum or not initialized"); ThreadContext *thread = global_->threads_[threadNum]; { std::unique_lock lock(thread->mutex); @@ -172,5 +174,9 @@ int ThreadManager::GetNumLooperThreads() const { } void ThreadManager::TryCancelTask(uint64_t taskID) { - // Do nothing + // Do nothing for now, just let it finish. +} + +bool ThreadManager::IsInitialized() const { + return !global_->threads_.empty(); } diff --git a/Common/Thread/ThreadManager.h b/Common/Thread/ThreadManager.h index 822997d921..683451da8e 100644 --- a/Common/Thread/ThreadManager.h +++ b/Common/Thread/ThreadManager.h @@ -48,6 +48,8 @@ public: void EnqueueTaskOnThread(int threadNum, Task *task, TaskType taskType); void Teardown(); + bool IsInitialized() const; + // Currently does nothing. It will always be best-effort - maybe it cancels, // maybe it doesn't. Note that the id is the id() returned by the task. You need to make that // something meaningful yourself. @@ -58,7 +60,8 @@ public: int GetNumLooperThreads() const; private: - GlobalThreadContext *global_ = nullptr; + // This is always pointing to a context, initialized in the constructor. + GlobalThreadContext *global_; int numThreads_ = 0; int numComputeThreads_ = 0; diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index cd0667829b..13093046a0 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -71,7 +71,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin } if (!fileLoader->Exists()) { - *errorString = "IdentifyFile: File doesn't exist" + fileLoader->GetPath().ToString(); + *errorString = "IdentifyFile: File doesn't exist: " + fileLoader->GetPath().ToString(); return IdentifiedFileType::ERROR_IDENTIFYING; } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 80e6570850..5d4d8c1b76 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -46,6 +46,7 @@ #include "Common/Net/HTTPClient.h" #include "Common/Net/Resolve.h" +#include "Common/Net/URL.h" #include "Common/Render/TextureAtlas.h" #include "Common/Render/Text/draw_text.h" #include "Common/GPU/OpenGL/GLFeatures.h" @@ -683,7 +684,14 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } } if (okToLoad) { - boot_filename = Path(std::string(argv[i])); + std::string str = std::string(argv[i]); + // Handle file:/// URIs, since you get those when creating shortcuts on some Android systems. + if (startsWith(str, "file:///")) { + str = UriDecode(str.substr(7)); + INFO_LOG(IO, "Decoding '%s' to '%s'", argv[i], str.c_str()); + } + + boot_filename = Path(str); skipLogo = true; } if (okToLoad && okToCheck) { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index da4d591113..7b2f2079f2 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -1441,15 +1441,30 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J return true; } +// NOTE: This is defunct and not working, due to how the Android storage functions currently require +// a PpssppActivity specifically and we don't have one here. extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv *env, jclass, jstring jpath) { + bool teardownThreadManager = false; + if (!g_threadManager.IsInitialized()) { + INFO_LOG(SYSTEM, "No thread manager - initializing one"); + // Need a thread manager. + teardownThreadManager = true; + g_threadManager.Init(1, 1); + } + Path path = Path(GetJavaString(env, jpath)); + + INFO_LOG(SYSTEM, "queryGameName(%s)", path.c_str()); + std::string result = ""; GameInfoCache *cache = new GameInfoCache(); std::shared_ptr info = cache->GetInfo(nullptr, path, 0); // Wait until it's done: this is synchronous, unfortunately. if (info) { + INFO_LOG(SYSTEM, "GetInfo successful, waiting"); cache->WaitUntilDone(info); + INFO_LOG(SYSTEM, "Done waiting"); if (info->fileType != IdentifiedFileType::UNKNOWN) { result = info->GetTitle(); @@ -1458,9 +1473,19 @@ extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv if (result.length() > strlen("The ") && startsWithNoCase(result, "The ")) { result = result.substr(strlen("The ")); } + + INFO_LOG(SYSTEM, "queryGameName: Got '%s'", result.c_str()); + } else { + INFO_LOG(SYSTEM, "queryGameName: Filetype unknown"); } + } else { + INFO_LOG(SYSTEM, "No info from cache"); } delete cache; + if (teardownThreadManager) { + g_threadManager.Teardown(); + } + return env->NewStringUTF(result.c_str()); } diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index f7786ef707..9f55c799cc 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -98,7 +98,7 @@ public class PpssppActivity extends NativeActivity { // String action = intent.getAction(); Uri data = intent.getData(); if (data != null) { - String path = intent.getData().getPath(); + String path = data.toString(); Log.i(TAG, "Found Shortcut Parameter in data: " + path); super.setShortcutParam("\"" + path.replace("\\", "\\\\").replace("\"", "\\\"") + "\""); // Toast.makeText(getApplicationContext(), path, Toast.LENGTH_SHORT).show(); diff --git a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java index 8b33203d7e..d21c3bddca 100644 --- a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java +++ b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java @@ -5,11 +5,13 @@ import android.app.AlertDialog; import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Looper; import android.util.Log; import java.io.File; +import java.nio.charset.StandardCharsets; /** * This class will respond to android.intent.action.CREATE_SHORTCUT intent from launcher homescreen. @@ -18,35 +20,114 @@ import java.io.File; public class ShortcutActivity extends Activity { private static final String TAG = "PPSSPP"; + private boolean scoped = false; + private static final int RESULT_OPEN_DOCUMENT = 2; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Show file selector dialog here. - SimpleFileChooser fileDialog = new SimpleFileChooser(this, Environment.getExternalStorageDirectory(), onFileSelectedListener); - fileDialog.showDialog(); + // Show file selector dialog here. If Android version is more than or equal to 11, + // use the native document file browser instead of our SimpleFileChooser. + scoped = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R); + + if (scoped) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + // Possible alternative approach: + // String[] mimeTypes = {"application/octet-stream", "/x-iso9660-image"}; + // intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + startActivityForResult(intent, RESULT_OPEN_DOCUMENT); + // intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); + Log.i(TAG, "Starting open document activity"); + } catch (Exception e) { + Log.e(TAG, e.toString()); + } + } else { + SimpleFileChooser fileDialog = new SimpleFileChooser(this, Environment.getExternalStorageDirectory(), onFileSelectedListener); + fileDialog.showDialog(); + } + } + + // Respond to native file dialog. + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == RESULT_OPEN_DOCUMENT && data != null) { + Uri selectedFile = data.getData(); + if (selectedFile != null) { + // Grab permanent permission so we can show it in recents list etc. + if (Build.VERSION.SDK_INT >= 19) { + Log.i(TAG, "Taking URI permission"); + getContentResolver().takePersistableUriPermission(selectedFile, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + Log.i(TAG, "Browse file finished:" + selectedFile.toString()); + respondToShortcutRequest(selectedFile); // finishes. + return; + } + } + + // We're done, no matter how it went. + finish(); } public static native String queryGameName(String path); // Create shortcut as response for ACTION_CREATE_SHORTCUT intent. - private void respondToShortcutRequest(String path) { + private void respondToShortcutRequest(Uri uri) { // This is Intent that will be sent when user execute our shortcut on // homescreen. Set our app as target Context. Set Main activity as // target class. Add any parameter as data. - Intent shortcutIntent = new Intent(this, PpssppActivity.class); - Uri uri = Uri.fromFile(new File(path)); + Intent shortcutIntent = new Intent(getApplicationContext(), PpssppActivity.class); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); Log.i(TAG, "Shortcut URI: " + uri.toString()); shortcutIntent.setData(uri); - + String path = uri.toString(); shortcutIntent.putExtra(PpssppActivity.SHORTCUT_EXTRA_KEY, path); + // We can't call C++ functions here that use storage APIs since there's no + // NativeActivity and all the AndroidStorage methods are methods on that. + // Should probably change that. In the meantime, let's just process the URI to make + // up a name. + + String name = "PPSSPP Game"; + String pathStr = "PPSSPP Game"; + if (path.startsWith("content://")) { + String [] segments = path.split("/"); + try { + pathStr = java.net.URLDecoder.decode(segments[segments.length - 1], StandardCharsets.UTF_8.name()); + } catch (Exception e) { + Log.i(TAG, "Exception getting name: " + e); + } + } else if (path.startsWith("file:///")) { + try { + pathStr = java.net.URLDecoder.decode(path.substring(7), StandardCharsets.UTF_8.name()); + } catch (Exception e) { + Log.i(TAG, "Exception getting name: " + e); + } + } else { + pathStr = path; + } + + String[] pathSegments = pathStr.split("/"); + name = pathSegments[pathSegments.length - 1]; + + /* + // No longer working for various reasons. + PpssppActivity.CheckABIAndLoadLibrary(); String name = queryGameName(path); if (name.equals("")) { + Log.i(TAG, "Failed to retrieve game name - ignoring."); showBadGameMessage(); return; - } + }*/ + + Log.i(TAG, "Game name: " + name + " : Creating shortcut to " + uri.toString()); // This is Intent that will be returned by this method, as response to // ACTION_CREATE_SHORTCUT. Wrap shortcut intent inside this intent. @@ -80,8 +161,6 @@ public class ShortcutActivity extends Activity { } catch (InterruptedException e) { e.printStackTrace(); } - - System.exit(-1); } // Event when a file is selected on file dialog. @@ -89,7 +168,8 @@ public class ShortcutActivity extends Activity { @Override public void onFileSelected(File file) { // create shortcut using file path - respondToShortcutRequest(file.getAbsolutePath()); + Uri uri = Uri.fromFile(new File(file.getAbsolutePath())); + respondToShortcutRequest(uri); } }; }