Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add is_emulator field
  • Loading branch information
mhsmith committed Mar 26, 2024
commit 4f65e8e620f4d5e95269ee100f991827f886c9d4
7 changes: 5 additions & 2 deletions Doc/library/platform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ Linux Platforms
Android Platform
----------------

.. function:: android_ver(release="", api_level=0, \
manufacturer="", model="", device="")
.. function:: android_ver(release="", api_level=0, manufacturer="", \
model="", device="", is_emulator=False)

Get Android device information. Returns a :func:`~collections.namedtuple`
with the following attributes. Values which cannot be determined are set to
Expand All @@ -328,6 +328,9 @@ Android Platform
<https://developer.android.com/reference/android/os/Build#DEVICE>`__ –
typically the model number or a codename

* ``is_emulator`` - ``True`` if the device is an emulator; ``False`` if it's
a physical device

For a list of known model and device names, see `here
<https://storage.googleapis.com/play_public/supported_devices.html>`__.

Expand Down
14 changes: 10 additions & 4 deletions Lib/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,10 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):


AndroidVer = collections.namedtuple(
"AndroidVer", "release api_level manufacturer model device")
"AndroidVer", "release api_level manufacturer model device is_emulator")

def android_ver(release="", api_level=0, manufacturer="", model="", device=""):
def android_ver(release="", api_level=0, manufacturer="", model="", device="",
is_emulator=False):
if sys.platform == "android":
try:
from ctypes import CDLL, c_char_p, create_string_buffer
Comment thread
mhsmith marked this conversation as resolved.
Expand All @@ -560,10 +561,13 @@ def android_ver(release="", api_level=0, manufacturer="", model="", device=""):
system_property_get.argtypes = (c_char_p, c_char_p)

def getprop(name, default):
PROP_VALUE_MAX = 92 # From sys/system_properties.h
# https://android.googlesource.com/platform/bionic/+/refs/tags/android-5.0.0_r1/libc/include/sys/system_properties.h#39
PROP_VALUE_MAX = 92
buffer = create_string_buffer(PROP_VALUE_MAX)
length = system_property_get(name.encode("UTF-8"), buffer)
if length == 0:
# This API doesn’t distinguish between an empty property and
# a missing one.
return default
else:
return buffer.value.decode("UTF-8", "backslashreplace")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you consider "surrogateescape" to not lose information? The caller can then decide how to handle surrogate characters. Did you see strings which cannot be decoded from UTF-8 in practice?

@mhsmith mhsmith Mar 27, 2024 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen such strings, but this function is likely to be used when building error reports, so perfect round-tripping is less important than making sure it doesn't cause any errors itself.

For that reason, I'd prefer not to use surrogateescape, because surrogates can't be turned back into bytes unless the encode call uses surrogateescape as well, which is something we have no control over, and something the programmer is very unlikely to test.

Expand All @@ -573,8 +577,10 @@ def getprop(name, default):
manufacturer = getprop("ro.product.manufacturer", manufacturer)
model = getprop("ro.product.model", model)
device = getprop("ro.product.device", device)
is_emulator = getprop("ro.kernel.qemu", "0") == "1"

return AndroidVer(release, api_level, manufacturer, model, device)
return AndroidVer(
release, api_level, manufacturer, model, device, is_emulator)


### System name aliasing
Expand Down
16 changes: 8 additions & 8 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1791,18 +1791,18 @@ def missing_compiler_executable(cmd_names=[]):
return cmd[0]


_is_android_emulator = None
_old_android_emulator = None
def setswitchinterval(interval):
# Setting a very low gil interval on the Android emulator causes python
# to hang (issue #26939).
minimum_interval = 1e-5
minimum_interval = 1e-4
Comment thread
mhsmith marked this conversation as resolved.
Outdated
if is_android and interval < minimum_interval:
global _is_android_emulator
if _is_android_emulator is None:
import subprocess
_is_android_emulator = (subprocess.check_output(
['getprop', 'ro.kernel.qemu']).strip() == b'1')
if _is_android_emulator:
global _old_android_emulator
if _old_android_emulator is None:
import platform
av = platform.android_ver()
_old_android_emulator = av.is_emulator and av.api_level < 24
if _old_android_emulator:
interval = minimum_interval
return sys.setswitchinterval(interval)

Expand Down
14 changes: 9 additions & 5 deletions Lib/test/test_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,35 +446,39 @@ def test_libc_ver(self):
def test_android_ver(self):
res = platform.android_ver()
self.assertIsInstance(res, tuple)
self.assertEqual(res, (res.release, res.api_level,
res.manufacturer, res.model, res.device))
self.assertEqual(res, (res.release, res.api_level, res.manufacturer,
res.model, res.device, res.is_emulator))

if sys.platform == "android":
for name in ["release", "manufacturer", "model", "device"]:
with self.subTest(name):
value = getattr(res, name)
self.assertIsInstance(value, str)
self.assertGreater(len(value), 0)
self.assertNotEqual(value, "")

self.assertIsInstance(res.api_level, int)
self.assertGreaterEqual(res.api_level, sys.getandroidapilevel())

self.assertIsInstance(res.is_emulator, bool)

# When not running on Android, it should return the default values.
else:
self.assertEqual(res.release, "")
self.assertEqual(res.api_level, 0)
self.assertEqual(res.manufacturer, "")
self.assertEqual(res.model, "")
self.assertEqual(res.device, "")
self.assertEqual(res.is_emulator, False)

# Default values may also be overridden using parameters.
res = platform.android_ver("alpha", 1, "bravo", "charlie", "delta")
res = platform.android_ver(
"alpha", 1, "bravo", "charlie", "delta", True)
self.assertEqual(res.release, "alpha")
self.assertEqual(res.api_level, 1)
self.assertEqual(res.manufacturer, "bravo")
self.assertEqual(res.model, "charlie")
self.assertEqual(res.device, "delta")

self.assertEqual(res.is_emulator, True)

@support.cpython_only
def test__comparable_version(self):
Expand Down