Unauthorized file access although `MLPermission.ReadExternalStorage` was requested and granted

Hello there Magic Leap community,

I'm currently trying to host a small web server within my Unity App to allow accessing a debug interface by directly connecting to the ML via a web browser.
I'm running into an issue where I can't seem to figure out how get the required permissions for accessing a folder previously copied to the device.

Things I have done so far:

  • add <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> to the manifest file.
  • check required permissions during app startup and request them via the ML SDK interface if not present (app shows up with storage permissions in the permission manager after initially approving).
  • tried different location within the /sdcard directory.

Sadly, the web server always returns a 404 error as the read operation on the directory which should be served fails (see error below).

Admittedly I'm not very experienced with Android development but as far as I understand, the external storage should be an alias for everything within the /sdcard folder on the device or am I getting this wrong?

Thank you in advance!

Unity Editor version: 2022.2.0b16
ML2 OS version: 1.1.0
MLSDK version: 1.2.0
Host OS: Windows

Error messages from logs (syntax-highlighting is supported via Markdown):

System.UnauthorizedAccessException: Access to the path "/sdcard/Public/debug-client/index.html" is denied.
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at System.IO.FileStream..ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.IO.FileOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at System.IO.StreamReader..ctor (System.String path, System.Text.Encoding encoding, System.Boolean detectEncodingFromByteOrderMarks, System.Int32 bufferSize) [0x00000] in <00000000000000000000000000000000>:0 
  at System.IO.File.InternalReadAllText (System.String path, System.Text.Encoding encoding) [0x00000] in <00000000000000000000000000000000>:0 
  at Websockets.Server.DebugServer+<>c__DisplayClass5_0.<.ctor>b__0 (System.Object sender, WebSocketSharp.Server.HttpRequestEventArgs e) [0x00000] in <00000000000000000000000000000000>:0 
  at WebSocketSharp.Server.HttpServer.processRequest (WebSocketSharp.Net.HttpListenerContext context) [0x00000] in <00000000000000000000000000000000>:0 
  at WebSocketSharp.Server.HttpServer+<>c__DisplayClass87_0.<receiveRequest>b__0 (System.Object state) [0x00000] in <00000000000000000000000000000000>:0 
  at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, System.Object state, System.Boolean preserveSyncCtx) [0x00000] in <00000000000000000000000000000000>:0 
  at System.Threading.ThreadPoolWorkQueue.Dispatch () [0x00000] in <00000000000000000000000000000000>:0 

The manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools">
  <application>
    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:theme="@style/UnityThemeSelector">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
    </activity>
  </application>
  <uses-permission android:name="com.magicleap.permission.SPATIAL_MAPPING" />
  <uses-permission android:name="com.magicleap.permission.SPATIAL_ANCHOR" />
  <uses-permission android:name="com.magicleap.permission.HAND_TRACKING" />
  <uses-permission android:name="com.magicleap.permission.WEBVIEW" />
  <uses-permission android:name="com.magicleap.permission.MARKER_TRACKING" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="com.magicleap.permission.VOICE_INPUT" />
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

Mono Behaviour permissions snippet:

    void Start()
    {
        if (MLPermissions.CheckPermission(MLPermission.ReadExternalStorage).IsOk)
        {
            InitServer();
        }
        else
        {
            MLPermissions.RequestPermission(MLPermission.ReadExternalStorage, permissionCallbacks);
        }
    }

Hi @julian.schlenker, let me look into this and I'll report back. Thank you for your post.

1 Like

Hi @julian.schlenker Thank you for your post. I had a conversation with our engineering team. Have you tried adb reverse? We use that frequently and run a simple web server using:

python -m http.server 8080

where the above command will use it's current directory as the root of the web server.

The python web server would run on your machine, but you could talk to it with your app and send JSON data and other such things.

External storage is heavily restricted, per Android documentation: Generally, apps can not read another app's data, but they can get access to media, but that's mostly about it.

Example adb reverse command:

adb reverse tcp:8080 tcp:8080

So now if your app connects to https://localhost:8080 you will get the web server, if you started it at port 8080 on your machine. Here is an example:

(base) 
user@path/to/repo/ml2_aosp_$ adb shell 
demophon:/ $ telnet localhost:8080 
HEAD / HTTP/1.0 

HTTP/1.0 200 OK Server: SimpleHTTP/0.6 Python/3.9.7 
Date: Tue, 17 Jan 2023 19:22:10 GMT 
Content-type: text/html; charset=utf-8 Content-Length: 1903

Please let me know if this unblocks you.

1 Like

Hey @kdowney ,

thank you for the response. It seems we have a bit of a misunderstanding.
My goal is to serve a website which connects to the app/ML2 via a websocket to allow for easy manipulation of some parameters and report some data back.
While this could be achieved w/o actually serving the website from the ML2 and only providing a websocket in the ML2 app, it is way more convenient if everything is served from the device(s) in our scenario.

So the essential question is how I can read any files from the /sdcard directory (or any sub directories) from within a Unity application while READ_EXTERNAL_STORAGE is permitted.

1 Like

Thank you @julian.schlenker. According to the engineer with whom I spoke, I believe this should help: Data and file storage overview | Android Developers

The engineer said you may need something like this to make it work, but you'd need the Unity equivalent: Android permission to read a file from SD card issues

Let us know if you're successful. Thanks for your post.

1 Like

Hey @kdowney ,

thanks for the response and sorry for the long absence.
As stated in my initial post I am aware of these problems but I can't seem to figure them out.
Permissions are requested and granted and I see no reason for the read access to error.

The android docs also state:

Caution: The exact location of where your files can be saved might vary across devices. For this reason, don't use hard-coded file paths.

This makes me wonder if I'm misunderstanding the location of external storage on the ML2.
If you could provide some example snippets were you actually access files from the device from within a Unity Application this would be greatly appreciated and helpful.

Hi there @julian.schlenker, no unfortunately we don't have ready-made example code for external storage to share.