mirror of
https://github.com/python/cpython.git
synced 2025-09-26 10:19:53 +00:00
gh-71042: Add platform.android_ver
(#116674)
This commit is contained in:
parent
ce00de4c8c
commit
74c8568d07
9 changed files with 164 additions and 16 deletions
|
@ -301,3 +301,39 @@ Linux Platforms
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
.. versionadded:: 3.10
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
|
||||||
|
Android Platform
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
the defaults given as parameters.
|
||||||
|
|
||||||
|
* ``release`` - Android version, as a string (e.g. ``"14"``).
|
||||||
|
|
||||||
|
* ``api_level`` - API level of the running device, as an integer (e.g. ``34``
|
||||||
|
for Android 14). To get the API level which Python was built against, see
|
||||||
|
:func:`sys.getandroidapilevel`.
|
||||||
|
|
||||||
|
* ``manufacturer`` - `Manufacturer name
|
||||||
|
<https://developer.android.com/reference/android/os/Build#MANUFACTURER>`__.
|
||||||
|
|
||||||
|
* ``model`` - `Model name
|
||||||
|
<https://developer.android.com/reference/android/os/Build#MODEL>`__ –
|
||||||
|
typically the marketing name or model number.
|
||||||
|
|
||||||
|
* ``device`` - `Device name
|
||||||
|
<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.
|
||||||
|
|
||||||
|
Google maintains a `list of known model and device names
|
||||||
|
<https://storage.googleapis.com/play_public/supported_devices.html>`__.
|
||||||
|
|
||||||
|
.. versionadded:: 3.13
|
||||||
|
|
|
@ -753,7 +753,9 @@ always available.
|
||||||
|
|
||||||
.. function:: getandroidapilevel()
|
.. function:: getandroidapilevel()
|
||||||
|
|
||||||
Return the build time API version of Android as an integer.
|
Return the build-time API level of Android as an integer. This represents the
|
||||||
|
minimum version of Android this build of Python can run on. For runtime
|
||||||
|
version information, see :func:`platform.android_ver`.
|
||||||
|
|
||||||
.. availability:: Android.
|
.. availability:: Android.
|
||||||
|
|
||||||
|
|
|
@ -542,6 +542,47 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
|
||||||
|
|
||||||
return release, vendor, vminfo, osinfo
|
return release, vendor, vminfo, osinfo
|
||||||
|
|
||||||
|
|
||||||
|
AndroidVer = collections.namedtuple(
|
||||||
|
"AndroidVer", "release api_level manufacturer model device is_emulator")
|
||||||
|
|
||||||
|
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
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# An NDK developer confirmed that this is an officially-supported
|
||||||
|
# API (https://stackoverflow.com/a/28416743). Use `getattr` to avoid
|
||||||
|
# private name mangling.
|
||||||
|
system_property_get = getattr(CDLL("libc.so"), "__system_property_get")
|
||||||
|
system_property_get.argtypes = (c_char_p, c_char_p)
|
||||||
|
|
||||||
|
def getprop(name, default):
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
release = getprop("ro.build.version.release", release)
|
||||||
|
api_level = int(getprop("ro.build.version.sdk", api_level))
|
||||||
|
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, is_emulator)
|
||||||
|
|
||||||
|
|
||||||
### System name aliasing
|
### System name aliasing
|
||||||
|
|
||||||
def system_alias(system, release, version):
|
def system_alias(system, release, version):
|
||||||
|
@ -972,6 +1013,11 @@ def uname():
|
||||||
system = 'Windows'
|
system = 'Windows'
|
||||||
release = 'Vista'
|
release = 'Vista'
|
||||||
|
|
||||||
|
# On Android, return the name and version of the OS rather than the kernel.
|
||||||
|
if sys.platform == 'android':
|
||||||
|
system = 'Android'
|
||||||
|
release = android_ver().release
|
||||||
|
|
||||||
vals = system, node, release, version, machine
|
vals = system, node, release, version, machine
|
||||||
# Replace 'unknown' values with the more portable ''
|
# Replace 'unknown' values with the more portable ''
|
||||||
_uname_cache = uname_result(*map(_unknown_as_blank, vals))
|
_uname_cache = uname_result(*map(_unknown_as_blank, vals))
|
||||||
|
|
|
@ -179,6 +179,9 @@ def collect_platform(info_add):
|
||||||
info_add(f'platform.freedesktop_os_release[{key}]',
|
info_add(f'platform.freedesktop_os_release[{key}]',
|
||||||
os_release[key])
|
os_release[key])
|
||||||
|
|
||||||
|
if sys.platform == 'android':
|
||||||
|
call_func(info_add, 'platform.android_ver', platform, 'android_ver')
|
||||||
|
|
||||||
|
|
||||||
def collect_locale(info_add):
|
def collect_locale(info_add):
|
||||||
import locale
|
import locale
|
||||||
|
|
|
@ -1801,18 +1801,18 @@ def missing_compiler_executable(cmd_names=[]):
|
||||||
return cmd[0]
|
return cmd[0]
|
||||||
|
|
||||||
|
|
||||||
_is_android_emulator = None
|
_old_android_emulator = None
|
||||||
def setswitchinterval(interval):
|
def setswitchinterval(interval):
|
||||||
# Setting a very low gil interval on the Android emulator causes python
|
# Setting a very low gil interval on the Android emulator causes python
|
||||||
# to hang (issue #26939).
|
# to hang (issue #26939).
|
||||||
minimum_interval = 1e-5
|
minimum_interval = 1e-4 # 100 us
|
||||||
if is_android and interval < minimum_interval:
|
if is_android and interval < minimum_interval:
|
||||||
global _is_android_emulator
|
global _old_android_emulator
|
||||||
if _is_android_emulator is None:
|
if _old_android_emulator is None:
|
||||||
import subprocess
|
import platform
|
||||||
_is_android_emulator = (subprocess.check_output(
|
av = platform.android_ver()
|
||||||
['getprop', 'ro.kernel.qemu']).strip() == b'1')
|
_old_android_emulator = av.is_emulator and av.api_level < 24
|
||||||
if _is_android_emulator:
|
if _old_android_emulator:
|
||||||
interval = minimum_interval
|
interval = minimum_interval
|
||||||
return sys.setswitchinterval(interval)
|
return sys.setswitchinterval(interval)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import errno
|
import errno
|
||||||
import math
|
import math
|
||||||
|
import platform
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
@ -1430,6 +1431,10 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
|
||||||
self._test_create_connection_ip_addr(m_socket, False)
|
self._test_create_connection_ip_addr(m_socket, False)
|
||||||
|
|
||||||
@patch_socket
|
@patch_socket
|
||||||
|
@unittest.skipIf(
|
||||||
|
support.is_android and platform.android_ver().api_level < 23,
|
||||||
|
"Issue gh-71123: this fails on Android before API level 23"
|
||||||
|
)
|
||||||
def test_create_connection_service_name(self, m_socket):
|
def test_create_connection_service_name(self, m_socket):
|
||||||
m_socket.getaddrinfo = socket.getaddrinfo
|
m_socket.getaddrinfo = socket.getaddrinfo
|
||||||
sock = m_socket.socket.return_value
|
sock = m_socket.socket.return_value
|
||||||
|
|
|
@ -219,6 +219,19 @@ class PlatformTest(unittest.TestCase):
|
||||||
self.assertEqual(res[-1], res.processor)
|
self.assertEqual(res[-1], res.processor)
|
||||||
self.assertEqual(len(res), 6)
|
self.assertEqual(len(res), 6)
|
||||||
|
|
||||||
|
if os.name == "posix":
|
||||||
|
uname = os.uname()
|
||||||
|
self.assertEqual(res.node, uname.nodename)
|
||||||
|
self.assertEqual(res.version, uname.version)
|
||||||
|
self.assertEqual(res.machine, uname.machine)
|
||||||
|
|
||||||
|
if sys.platform == "android":
|
||||||
|
self.assertEqual(res.system, "Android")
|
||||||
|
self.assertEqual(res.release, platform.android_ver().release)
|
||||||
|
else:
|
||||||
|
self.assertEqual(res.system, uname.sysname)
|
||||||
|
self.assertEqual(res.release, uname.release)
|
||||||
|
|
||||||
@unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
|
@unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
|
||||||
def test_uname_win32_without_wmi(self):
|
def test_uname_win32_without_wmi(self):
|
||||||
def raises_oserror(*a):
|
def raises_oserror(*a):
|
||||||
|
@ -458,6 +471,43 @@ class PlatformTest(unittest.TestCase):
|
||||||
self.assertEqual(platform.libc_ver(filename, chunksize=chunksize),
|
self.assertEqual(platform.libc_ver(filename, chunksize=chunksize),
|
||||||
('glibc', '1.23.4'))
|
('glibc', '1.23.4'))
|
||||||
|
|
||||||
|
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, 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.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", 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
|
@support.cpython_only
|
||||||
def test__comparable_version(self):
|
def test__comparable_version(self):
|
||||||
from platform import _comparable_version as V
|
from platform import _comparable_version as V
|
||||||
|
|
|
@ -209,7 +209,10 @@ HAVE_SOCKET_QIPCRTR = _have_socket_qipcrtr()
|
||||||
|
|
||||||
HAVE_SOCKET_VSOCK = _have_socket_vsock()
|
HAVE_SOCKET_VSOCK = _have_socket_vsock()
|
||||||
|
|
||||||
HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE")
|
# Older Android versions block UDPLITE with SELinux.
|
||||||
|
HAVE_SOCKET_UDPLITE = (
|
||||||
|
hasattr(socket, "IPPROTO_UDPLITE")
|
||||||
|
and not (support.is_android and platform.android_ver().api_level < 29))
|
||||||
|
|
||||||
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
|
HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth()
|
||||||
|
|
||||||
|
@ -1217,8 +1220,8 @@ class GeneralModuleTests(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
raise OSError
|
raise OSError
|
||||||
# Try same call with optional protocol omitted
|
# Try same call with optional protocol omitted
|
||||||
# Issue #26936: Android getservbyname() was broken before API 23.
|
# Issue gh-71123: this fails on Android before API level 23.
|
||||||
if (not support.is_android) or sys.getandroidapilevel() >= 23:
|
if not (support.is_android and platform.android_ver().api_level < 23):
|
||||||
port2 = socket.getservbyname(service)
|
port2 = socket.getservbyname(service)
|
||||||
eq(port, port2)
|
eq(port, port2)
|
||||||
# Try udp, but don't barf if it doesn't exist
|
# Try udp, but don't barf if it doesn't exist
|
||||||
|
@ -1229,8 +1232,9 @@ class GeneralModuleTests(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
eq(udpport, port)
|
eq(udpport, port)
|
||||||
# Now make sure the lookup by port returns the same service name
|
# Now make sure the lookup by port returns the same service name
|
||||||
# Issue #26936: Android getservbyport() is broken.
|
# Issue #26936: when the protocol is omitted, this fails on Android
|
||||||
if not support.is_android:
|
# before API level 28.
|
||||||
|
if not (support.is_android and platform.android_ver().api_level < 28):
|
||||||
eq(socket.getservbyport(port2), service)
|
eq(socket.getservbyport(port2), service)
|
||||||
eq(socket.getservbyport(port, 'tcp'), service)
|
eq(socket.getservbyport(port, 'tcp'), service)
|
||||||
if udpport is not None:
|
if udpport is not None:
|
||||||
|
@ -1575,8 +1579,8 @@ class GeneralModuleTests(unittest.TestCase):
|
||||||
socket.getaddrinfo('::1', 80)
|
socket.getaddrinfo('::1', 80)
|
||||||
# port can be a string service name such as "http", a numeric
|
# port can be a string service name such as "http", a numeric
|
||||||
# port number or None
|
# port number or None
|
||||||
# Issue #26936: Android getaddrinfo() was broken before API level 23.
|
# Issue #26936: this fails on Android before API level 23.
|
||||||
if (not support.is_android) or sys.getandroidapilevel() >= 23:
|
if not (support.is_android and platform.android_ver().api_level < 23):
|
||||||
socket.getaddrinfo(HOST, "http")
|
socket.getaddrinfo(HOST, "http")
|
||||||
socket.getaddrinfo(HOST, 80)
|
socket.getaddrinfo(HOST, 80)
|
||||||
socket.getaddrinfo(HOST, None)
|
socket.getaddrinfo(HOST, None)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :func:`platform.android_ver`, which provides device and OS information
|
||||||
|
on Android.
|
Loading…
Add table
Add a link
Reference in a new issue