Repository: kivy/kivy

Issue:
Platform linux on some android devices
<!--
The issue tracker is a tool to address bugs.
Please use the Disord community or Stack Overflow for support questions,
more information at https://github.com/kivy/python-for-android#support

Before opening a new issue, make sure you do the following:
    * check that your issue isn't already filed: https://github.com/kivy/python-for-android/issues
    * prepare a short, runnable example that reproduces the issue
    * make sure to have `log_level = 2` in your `buildozer.spec`
    * reproduce the problem with the latest development version (`p4a.branch = master`)
    * double-check that the issue is indeed a bug and not a support request
    * please use backticks to format code or logs
-->

### Versions

* Kivy: master (2.0.0rc1)

### Description

Last week I had to rebuild a kivy app that was working in many android devices. I normally do it with buildozer docker image.
I was able to create the apk again and it works as expected ... in some devices! In other ones the app crashes during splash screen due to a PermissionError. 
After debugging with adb logcat I noticed kivy is not recognizing the platform correctly as it return 'linux' instead of 'android'.
This causes that [ANDROID_APP_PATH is not used](https://github.com/kivy/kivy/blob/b6eddbb2fcf97a21b7f0c4cfbf0d8ccdb74fb3ec/kivy/__init__.py#L339) and it tries to write on /data/ path.

I'm using kivy==master on buildozer.spec.

### Logs

```
04-27 14:54:56.946  7788  7807 I python  : Android kivy bootstrap done. name is main
04-27 14:54:56.946  7788  7807 I python  : AND: Ran string
04-27 14:54:56.946  7788  7807 I python  : Run user program, change dir and execute entrypoint
04-27 14:54:57.303  7788  7807 I python  :  Traceback (most recent call last):
04-27 14:54:57.303  7788  7807 I python  :    File "/home/user/hostcwd/.buildozer/android/app/main.py", line 10, in <module>
04-27 14:54:57.304  7788  7807 I python  :    File "/home/user/hostcwd/.buildozer/android/app/app.py", line 4, in <module>
04-27 14:54:57.305  7788  7807 I python  :    File "/home/use
r/hostcwd/.buildozer/android/platform/build-armeabi-v7a/build/python-installs/myapp/kivy/__init__.py", line 351, in <module>
04-27 14:54:57.306  7788  7807 I python  :  PermissionError: [Errno 13] Permission denied: '/data/.kivy'
04-27 14:54:57.306  7788  7807 I python  : Python for android ended.
```


Discussion:
AndreMiras: Thanks for the bugfix attempt. Obviously unit tests also need to be updated.
I would say if we could narrow it down to only the check you're adding and removing the other change that introduced the bug, the code would be simpler. That's just a suggestion.
However we also need @olivier-boesch to test this method. I guess this environment variable should be always available when built with p4a (do we have other cases). Unless there's something special on his setup dropping some environment variables
inclement: I'd like to propose the tidied up method at https://github.com/kivy/kivy/pull/6853 - in particular it seems sensible to always allow an env var to override the detected platform, it can't hurt as a debug tool. We can also transition p4a to do this as well as setting the P4A_BOOTSTRAP variable, that would seem neater.

I made a PR just to demonstrate because I didn't want to do a big edit to @opacam's proposal, please feel free to go ahead with this one and close mine.
olivier-boesch: Hi everyone,

I will perform some tests later when I have time to but, making a quick verification, I've found a use case when the logic of environment cars doesn't work: if you are working with pydroid3. In this case, no env vars mentionned are set. See output below.

Maybe a mixed approach? First verify env vars and, if needed, when paths?

Output:
[INFO   ] [Logger      ] Record log in /storage/emulated/0/Android/data/ru.iiec.pydroid3/files/.kivy/logs/kivy_20-05-02_2.txt
[INFO   ] [Kivy        ] v1.11.0.dev0, git-8b6af30, 20190320
[INFO   ] [Python      ] v3.7.2 (default, Mar 20 2019, 15:02:54) 
[GCC 8.2.0]
environ({'PATH': '/data/user/0/ru.iiec.pydroid3/files:/data/user/0/ru.iiec.pydroid3/files/bin:/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/bin:/busybox-virtual:/sbin:/system/sbin:/system/bin:/system/xbin:/vendor/bin:/vendor/xbin', 'DOWNLOAD_CACHE': '/data/cache', 'ANDROID_BOOTLOGO': '1', 'ANDROID_ROOT': '/system', 'ANDROID_ASSETS': '/system/app', 'ANDROID_DATA': '/data', 'ANDROID_STORAGE': '/storage', 'EXTERNAL_STORAGE': '/sdcard', 'ASEC_MOUNTPOINT': '/mnt/asec', 'BOOTCLASSPATH': '/system/framework/com.qualcomm.qti.camera.jar:/system/framework/QPerformance.jar:/system/framework/core-oj.jar:/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/bouncycastle.jar:/system/framework/apache-xml.jar:/system/framework/legacy-test.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/org.apache.http.legacy.boot.jar:/system/framework/android.hidl.base-V1.0-java.jar:/system/framework/android.hidl.manager-V1.0-java.jar:/system/framework/tcmiface.jar:/system/framework/WfdCommon.jar:/system/framework/oem-services.jar:/system/framework/qcom.fmradio.jar:/system/framework/telephony-ext.jar', 'SYSTEMSERVERCLASSPATH': '/system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar:/system/framework/com.android.location.provider.jar', 'ANDROID_SOCKET_zygote': '9', 'PKG_CONFIG_PATH': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/pkgconfig', 'SSL_CERT_FILE': '/data/user/0/ru.iiec.pydroid3/files/cert.pem', 'TERMINFO': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/share/terminfo', 'LD_LIBRARY_PATH': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib', 'TMPDIR': '/data/user/0/ru.iiec.pydroid3/cache', 'SHELL': '/data/user/0/ru.iiec.pydroid3/files/sh', 'CONFIG_SHELL': '/data/user/0/ru.iiec.pydroid3/files/sh', 'LIBRARY_PATH': '/data/user/0/ru.iiec.pydroid3/files/lib/gcc/aarch64-linux-android/8.2.0/', 'XDG_CACHE_HOME': '/data/user/0/ru.iiec.pydroid3/cache', 'APP_PACKAGE': '/data/app/ru.iiec.pydroid3-EdWP4V2iZJ6TydGHILDxKQ``/base.apk', 'LC_ALL': 'en_US.UTF-8', 'JOBLIB_MULTIPROCESSING': '0', 'KERAS_BACKEND': 'theano', 'PYDROID3_LICENSE': 'Accepted', 'PYDROID_RPC': '/data/user/0/ru.iiec.pydroid3/files/RPCServer.socket', 'PYDROID_PACKAGE': 'ru.iiec.pydroid3', 'BROWSER': 'mybrowser %s', 'ANDROID_PRIVATE': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android', 'ANDROID_ARGUMENT': '/storage/emulated/0/Android/data/ru.iiec.pydroid3/files', 'HOME': '/data/user/0/ru.iiec.pydroid3/app_HOME', 'PYDR_STDERROUT': '/data/user/0/ru.iiec.pydroid3/files/pydroid_stderrout_log', 'ANDROID_APP_PATH': '/storage/emulated/0/Android/data/ru.iiec.pydroid3/files', 'ANDROID_ORIGFNAME': '/data/user/0/ru.iiec.pydroid3/files/temp_iiec_codefile.py', 'ANDROID_ENTRYPOINT': '/data/user/0/ru.iiec.pydroid3/files/temp_iiec_codefile.py', 'PYTHONHOME': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android', 'PYTHONPATH': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android:/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib', 'PYSDL2_DLL_PATH': '/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib', 'PYTHON_NAME': 'python'})

inclement: In this case the `ANDROID_ARGUMENT` env var that was originally removed from kivy's check is present - which makes sense because pydroid3 is ensuring kivy will work.

We should definitely leave the ANDROID_ARGUMENT check in for backwards compatibility, with a comment about the fact that it used to be what we reliably used. @opacam could you add that back in as an alternative to P4A_BOOTSTRAP being present?

I'm not against adding other checks for being in an android environment, if necessary. Perhaps we should be using pyjnius to query the android api.
opacam: > We should definitely leave the ANDROID_ARGUMENT check in for backwards compatibility, with a comment about the fact that it used to be what we reliably used. @opacam could you add that back in as an alternative to P4A_BOOTSTRAP being present?

Yes, no problem

Patch:
diff --git a/kivy/utils.py b/kivy/utils.py
index 628c30598..7afbde0e0 100644
--- a/kivy/utils.py
+++ b/kivy/utils.py
@@ -424,15 +424,17 @@ def format_bytes_to_human(size, precision=2):
 
 def _get_platform():
     # On Android sys.platform returns 'linux2', so prefer to check the
-    # existence of system specific commands and files
-    if (
-            path.exists('/default.prop')
-            and path.exists('/system/bin/logcat')
-            and path.exists('/system/xbin')
-    ):
+    # existence of environ variables set during Python initialization
+    kivy_build = environ.get('KIVY_BUILD', '')
+    if kivy_build in {'android', 'ios'}:
+        return environ['KIVY_BUILD']
+    elif 'P4A_BOOTSTRAP' in environ:
+        return 'android'
+    elif 'ANDROID_ARGUMENT' in environ:
+        # We used to use this method to detect android platform,
+        # leaving it here to be backwards compatible with `pydroid3`
+        # and similar tools outside kivy's ecosystem
         return 'android'
-    elif environ.get('KIVY_BUILD', '') == 'ios':
-        return 'ios'
     elif _sys_platform in ('win32', 'cygwin'):
         return 'win'
     elif _sys_platform == 'darwin':


Test patch:
diff --git a/kivy/tests/test_utils.py b/kivy/tests/test_utils.py
index 0e5ad781c..afe18c8c7 100644
--- a/kivy/tests/test_utils.py
+++ b/kivy/tests/test_utils.py
@@ -191,21 +191,19 @@ class UtilsTest(unittest.TestCase):
         assert first == second
 
     def test_Platform_android(self):
-        # modified os.path.exists function
-        # for test purpose
-        def modified_path_exists(p):
-            valid_paths = ['/default.prop',
-                           '/system/bin/logcat',
-                           '/system/xbin']
-            if p in valid_paths:
-                return True
-            return False
-
-        with patch(target='os.path.exists',
-                   side_effect=modified_path_exists):
-            pf = _get_platform()
-            self.assertTrue(pf == 'android')
-        self.assertFalse(os.path.exists('/default.prop'))
+        with patch.dict('os.environ', {'KIVY_BUILD': 'android'}):
+            self.assertEqual(_get_platform(), 'android')
+        self.assertNotIn('KIVY_BUILD', os.environ)
+
+    def test_Platform_android_with_p4a(self):
+        with patch.dict('os.environ', {'P4A_BOOTSTRAP': 'sdl2'}):
+            self.assertEqual(_get_platform(), 'android')
+        self.assertNotIn('P4A_BOOTSTRAP', os.environ)
+
+    def test_Platform_android_with_android_argument(self):
+        with patch.dict('os.environ', {'ANDROID_ARGUMENT': ''}):
+            self.assertEqual(_get_platform(), 'android')
+        self.assertNotIn('ANDROID_ARGUMENT', os.environ)
 
     def test_Platform_ios(self):
         with patch.dict('os.environ', {'KIVY_BUILD': 'ios'}):