Browse Source

Add new JNI function to interface with AssetManager.list() method.
Fix #855.

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
ac2b2ba121

+ 68 - 0
Android/custom_rules.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2008-2015 the Urho3D project.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+-->
+<project name="Urho3D_custom_rules" default="help">
+
+    <!--
+        Patch assets directories to have a trailing '_' character in their names for easy identification
+    -->
+    <property name="patched-asset.dir" value="assets_" />
+    <property name="asset.absolute.dir" location="${patched-asset.dir}" />
+
+    <target name="-pre-build">
+        <delete dir="${patched-asset.dir}"/>
+        <mkdir dir="${patched-asset.dir}"/>
+        <apply executable="mkdir" dest="${patched-asset.dir}" addsourcefile="false">
+            <targetfile/>
+            <dirset dir="${asset.dir}"/>
+            <chainedmapper>
+                <regexpmapper from="(.+)" to="\1_"/>
+                <firstmatchmapper>
+                    <filtermapper>
+                        <replacestring from="/" to="_/"/>
+                    </filtermapper>
+                    <filtermapper>
+                        <replacestring from="\\" to="_\\"/>
+                    </filtermapper>
+                </firstmatchmapper>
+            </chainedmapper>
+        </apply>
+        <copy todir="${patched-asset.dir}">
+            <fileset dir="${asset.dir}">
+                <type type="file"/>
+            </fileset>
+            <firstmatchmapper>
+                <filtermapper>
+                    <replacestring from="/" to="_/"/>
+                </filtermapper>
+                <filtermapper>
+                    <replacestring from="\\" to="_\\"/>
+                </filtermapper>
+            </firstmatchmapper>
+        </copy>
+    </target>
+
+    <target name="-post-build">
+        <delete dir="${patched-asset.dir}"/>
+    </target>
+
+</project>

+ 1 - 1
CMake/Modules/Urho3D-CMake-common.cmake

@@ -1575,7 +1575,7 @@ if (ANDROID)
             create_symlink (${CMAKE_SOURCE_DIR}/bin/${I} ${CMAKE_SOURCE_DIR}/Android/assets/${I} FALLBACK_TO_COPY)
         endif ()
     endforeach ()
-    foreach (I AndroidManifest.xml build.xml src res assets jni)
+    foreach (I AndroidManifest.xml build.xml custom_rules.xml src res assets jni)
         if (EXISTS ${CMAKE_SOURCE_DIR}/Android/${I} AND NOT EXISTS ${CMAKE_BINARY_DIR}/${I})    # No-ops when 'Android' is used as build tree
             create_symlink (${CMAKE_SOURCE_DIR}/Android/${I} ${CMAKE_BINARY_DIR}/${I} FALLBACK_TO_COPY)
         endif ()

+ 65 - 3
Source/ThirdParty/SDL/src/core/android/SDL_android.c

@@ -19,7 +19,7 @@
   3. This notice may not be removed or altered from any source distribution.
 */
 
-// Modified by Lasse Oorni for Urho3D
+// Modified by Lasse Oorni and Yao Wei Tjong for Urho3D
 
 #include "../../SDL_internal.h"
 #include "SDL_stdinc.h"
@@ -1364,12 +1364,74 @@ void Android_JNI_HideTextInput()
 //////////////////////////////////////////////////////////////////////////////
 */
 
-void *SDL_AndroidGetJNIEnv()
+// Urho3D - function to return a list of files under a given path in "assets" directory (caller is responsible to free the C string array)
+char** SDL_Android_GetFileList(const char* path, int* count)
 {
-    return Android_JNI_GetEnv();
+    struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__);
+    JNIEnv* mEnv = Android_JNI_GetEnv();
+    if (!LocalReferenceHolder_Init(&refs, mEnv))
+    {
+        LocalReferenceHolder_Cleanup(&refs);
+        return NULL;
+    }
+
+    jstring pathJString = (*mEnv)->NewStringUTF(mEnv, path);
+
+    /* context = SDLActivity.getContext(); */
+    jmethodID mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass,
+            "getContext","()Landroid/content/Context;");
+    jobject context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid);
+
+    /* assetManager = context.getAssets(); */
+    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context),
+            "getAssets", "()Landroid/content/res/AssetManager;");
+    jobject assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid);
+
+    /* stringArray = assetManager.list(path) */
+    mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, assetManager), "list", "(Ljava/lang/String;)[Ljava/lang/String;");
+    jobjectArray stringArray = (*mEnv)->CallObjectMethod(mEnv, assetManager, mid, pathJString);
+    if (Android_JNI_ExceptionOccurred(true))
+    {
+        LocalReferenceHolder_Cleanup(&refs);
+        return NULL;
+    }
+
+    jsize arrayLength = (*mEnv)->GetArrayLength(mEnv, stringArray);
+    char** cStringArray = (char**)SDL_malloc(arrayLength * sizeof(char*));
+    jint i;
+    for (i = 0; i < arrayLength; ++i)
+    {
+        jstring string = (jstring)(*mEnv)->GetObjectArrayElement(mEnv, stringArray, i);
+        const char* cString = (*mEnv)->GetStringUTFChars(mEnv, string, 0);
+        cStringArray[i] = cString ? SDL_strdup(cString) : NULL;
+        (*mEnv)->ReleaseStringUTFChars(mEnv, string, cString);
+    }
+
+    if (count != NULL)
+        *count = arrayLength;
+
+    LocalReferenceHolder_Cleanup(&refs);
+    return cStringArray;
 }
 
+// Urho3D - helper function to free the file list returned by SDL_Android_GetFileList()
+void SDL_Android_FreeFileList(char*** array, int* count)
+{
+    int i = *count;
+    if ((i > 0) && (*array != NULL))
+    {
+        while (i--)
+            SDL_free((*array)[i]);
+    }
+    SDL_free(*array);
+    *array = NULL;
+    *count = 0;
+}
 
+void *SDL_AndroidGetJNIEnv()
+{
+    return Android_JNI_GetEnv();
+}
 
 void *SDL_AndroidGetActivity()
 {

+ 3 - 2
Source/Urho3D/IO/File.cpp

@@ -56,6 +56,7 @@ static const char* openMode[] =
 #endif
 
 #ifdef ANDROID
+const char* APK = "/apk/";
 static const unsigned READ_BUFFER_SIZE = 32768;
 #endif
 static const unsigned SKIP_BUFFER_SIZE = 1024;
@@ -130,7 +131,7 @@ bool File::Open(const String& fileName, FileMode mode)
     }
 
 #ifdef ANDROID
-    if (fileName.StartsWith("/apk/"))
+    if (fileName.StartsWith(APK))
     {
         if (mode != FILE_READ)
         {
@@ -138,7 +139,7 @@ bool File::Open(const String& fileName, FileMode mode)
             return false;
         }
 
-        assetHandle_ = SDL_RWFromFile(fileName.Substring(5).CString(), "rb");
+        assetHandle_ = SDL_RWFromFile(ASSET(fileName), "rb");
         if (!assetHandle_)
         {
             LOGERRORF("Could not open asset file %s", fileName.CString());

+ 7 - 0
Source/Urho3D/IO/File.h

@@ -34,6 +34,13 @@
 namespace Urho3D
 {
 
+#ifdef ANDROID
+extern const char* APK;
+
+// Macro for truncating the APK prefix string from the asset path name and at the same time patching the directory name components (see custom_rules.xml)
+#define ASSET(p) p.Substring(5).Replaced("/", "_/").CString()
+#endif
+
 /// File open mode.
 enum FileMode
 {

+ 76 - 16
Source/Urho3D/IO/FileSystem.cpp

@@ -60,13 +60,17 @@
 #include <mach-o/dyld.h>
 #endif
 
+extern "C"
+{
 #ifdef ANDROID
-extern "C" const char* SDL_Android_GetFilesDir();
-#endif
-#ifdef IOS
-extern "C" const char* SDL_IOS_GetResourceDir();
-extern "C" const char* SDL_IOS_GetDocumentsDir();
+const char* SDL_Android_GetFilesDir();
+char** SDL_Android_GetFileList(const char* path, int* count);
+void SDL_Android_FreeFileList(char*** array, int* count);
+#elif IOS
+const char* SDL_IOS_GetResourceDir();
+const char* SDL_IOS_GetDocumentsDir();
 #endif
+}
 
 #include "../DebugNew.h"
 
@@ -131,7 +135,7 @@ int DoSystemRun(const String& fileName, const Vector<String>& arguments)
     // Add .exe extension if no extension defined
     if (GetExtension(fixedFileName).Empty())
         fixedFileName += ".exe";
-    
+
     String commandLine = "\"" + fixedFileName + "\"";
     for (unsigned i = 0; i < arguments.Size(); ++i)
         commandLine += " " + arguments[i];
@@ -558,12 +562,10 @@ bool FileSystem::FileExists(const String& fileName) const
     if (!CheckAccess(GetPath(fileName)))
         return false;
 
-    String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
-
 #ifdef ANDROID
-    if (fixedName.StartsWith("/apk/"))
+    if (fileName.StartsWith(APK))
     {
-        SDL_RWops* rwOps = SDL_RWFromFile(fileName.Substring(5).CString(), "rb");
+        SDL_RWops* rwOps = SDL_RWFromFile(ASSET(fileName), "rb");
         if (rwOps)
         {
             SDL_RWclose(rwOps);
@@ -574,6 +576,8 @@ bool FileSystem::FileExists(const String& fileName) const
     }
 #endif
 
+    String fixedName = GetNativePath(RemoveTrailingSlash(fileName));
+
 #ifdef WIN32
     DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
     if (attributes == INVALID_FILE_ATTRIBUTES || attributes & FILE_ATTRIBUTE_DIRECTORY)
@@ -598,14 +602,37 @@ bool FileSystem::DirExists(const String& pathName) const
         return true;
 #endif
 
-    String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
-
 #ifdef ANDROID
-    /// \todo Actually check for existence, now true is always returned for directories within the APK
-    if (fixedName.StartsWith("/apk/"))
-        return true;
+    if (pathName.StartsWith(APK))
+    {
+        // Split the path name into two components: the longest directory path and the last name component
+        String assetPath(ASSET(pathName));
+        String parentPath;
+        unsigned pos = assetPath.FindLast('/');
+        if (pos != String::NPOS)
+        {
+            parentPath = assetPath.Substring(0, pos - 1);
+            assetPath = assetPath.Substring(pos + 1);
+        }
+        // The last name component is a directory name, so need to patch the name with trailing '_' here (see custom_rules.xml)
+        assetPath.Append('_');
+
+        bool exist = false;
+        int count;
+        char** list = SDL_Android_GetFileList(parentPath.CString(), &count);
+        for (int i = 0; i < count; ++i)
+        {
+            exist = assetPath == list[i];
+            if (exist)
+                break;
+        }
+        SDL_Android_FreeFileList(&list, &count);
+        return exist;
+    }
 #endif
 
+    String fixedName = GetNativePath(RemoveTrailingSlash(pathName));
+
 #ifdef WIN32
     DWORD attributes = GetFileAttributesW(WString(fixedName).CString());
     if (attributes == INVALID_FILE_ATTRIBUTES || !(attributes & FILE_ATTRIBUTE_DIRECTORY))
@@ -639,7 +666,7 @@ String FileSystem::GetProgramDir() const
 #if defined(ANDROID)
     // This is an internal directory specifier pointing to the assets in the .apk
     // Files from this directory will be opened using special handling
-    programDir_ = "/apk/";
+    programDir_ = APK;
     return programDir_;
 #elif defined(IOS)
     programDir_ = AddTrailingSlash(SDL_IOS_GetResourceDir());
@@ -756,6 +783,39 @@ void FileSystem::ScanDirInternal(Vector<String>& result, String path, const Stri
     if (filterExtension.Contains('*'))
         filterExtension.Clear();
 
+#ifdef ANDROID
+    if (path.StartsWith(APK))
+    {
+        String assetPath(ASSET(path));
+        assetPath.Resize(assetPath.Length() - 1);       // AssetManager does not like trailing slash
+        int count;
+        char** list = SDL_Android_GetFileList(assetPath.CString(), &count);
+        for (int i = 0; i < count; ++i)
+        {
+            String fileName(list[i]);
+            if (!(flags & SCAN_HIDDEN) && fileName.StartsWith("."))
+                continue;
+
+            // Patch the directory name back after retrieving the directory flag
+            bool isDirectory = fileName.EndsWith("_");
+            if (isDirectory)
+            {
+                fileName.Resize(fileName.Length() - 1);
+                if (flags & SCAN_DIRS)
+                    result.Push(deltaPath + fileName);
+                if (recursive)
+                    ScanDirInternal(result, path + fileName, startPath, filter, flags, recursive);
+            }
+            else if (flags & SCAN_FILES)
+            {
+                if (filterExtension.Empty() || fileName.EndsWith(filterExtension))
+                    result.Push(deltaPath + fileName);
+            }
+        }
+        SDL_Android_FreeFileList(&list, &count);
+        return;
+    }
+#endif
 #ifdef WIN32
     WIN32_FIND_DATAW info;
     HANDLE handle = FindFirstFileW(WString(path + "*").CString(), &info);

+ 2 - 2
Source/Urho3D/IO/PackageFile.cpp

@@ -53,7 +53,7 @@ PackageFile::~PackageFile()
 bool PackageFile::Open(const String& fileName, unsigned startOffset)
 {
 #ifdef ANDROID
-    if (fileName.StartsWith("/apk/"))
+    if (fileName.StartsWith(APK))
     {
         LOGERROR("Package files within the apk are not supported on Android");
         return false;
@@ -142,7 +142,7 @@ const PackageEntry* PackageFile::GetEntry(const String& fileName) const
     HashMap<String, PackageEntry>::ConstIterator i = entries_.Find(fileName);
     if (i != entries_.End())
         return &i->second_;
-    
+
 #ifdef WIN32
     // On Windows perform a fallback case-insensitive search
     else