2024-06-21 14:35:35 +08:00
|
|
|
# Copyright (C) 2024 The Android Open Source Project
|
|
|
|
|
#
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
2025-05-28 13:13:17 +00:00
|
|
|
import logging
|
2024-06-21 14:35:35 +08:00
|
|
|
import re
|
2025-05-28 13:13:17 +00:00
|
|
|
import time
|
|
|
|
|
import types
|
|
|
|
|
from mobly import utils
|
2024-06-21 14:35:35 +08:00
|
|
|
from mobly.controllers import android_device
|
2025-05-28 13:13:17 +00:00
|
|
|
from mobly.controllers.android_device_lib import adb
|
2024-06-21 14:35:35 +08:00
|
|
|
from net_tests_utils.host.python import assert_utils
|
|
|
|
|
|
2025-06-02 06:31:34 +00:00
|
|
|
BYTE_DECODE_UTF_8 = 'utf-8'
|
2024-06-21 14:35:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_doze_mode(ad: android_device.AndroidDevice, enable: bool) -> None:
|
|
|
|
|
if enable:
|
2025-06-02 06:31:34 +00:00
|
|
|
adb_shell(ad, 'cmd battery unplug')
|
2024-06-21 14:35:35 +08:00
|
|
|
expect_dumpsys_state_with_retry(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'deviceidle', key='mCharging', expected_state=False
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
|
|
|
|
_set_screen_state(ad, False)
|
2025-06-02 06:31:34 +00:00
|
|
|
adb_shell(ad, 'dumpsys deviceidle enable deep')
|
2024-06-21 14:35:35 +08:00
|
|
|
expect_dumpsys_state_with_retry(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'deviceidle', key='mDeepEnabled', expected_state=True
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
2025-06-02 06:31:34 +00:00
|
|
|
adb_shell(ad, 'dumpsys deviceidle force-idle deep')
|
2024-06-21 14:35:35 +08:00
|
|
|
expect_dumpsys_state_with_retry(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'deviceidle', key='mForceIdle', expected_state=True
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
|
|
|
|
else:
|
2025-06-02 06:31:34 +00:00
|
|
|
adb_shell(ad, 'cmd battery reset')
|
2024-06-21 14:35:35 +08:00
|
|
|
expect_dumpsys_state_with_retry(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'deviceidle', key='mCharging', expected_state=True
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
2025-06-02 06:31:34 +00:00
|
|
|
adb_shell(ad, 'dumpsys deviceidle unforce')
|
2024-06-21 14:35:35 +08:00
|
|
|
expect_dumpsys_state_with_retry(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'deviceidle', key='mForceIdle', expected_state=False
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_screen_state(
|
|
|
|
|
ad: android_device.AndroidDevice, target_state: bool
|
|
|
|
|
) -> None:
|
|
|
|
|
assert_utils.expect_with_retry(
|
|
|
|
|
predicate=lambda: _get_screen_state(ad) == target_state,
|
|
|
|
|
retry_action=lambda: adb_shell(
|
2025-06-02 06:31:34 +00:00
|
|
|
ad, 'input keyevent KEYCODE_POWER'
|
2024-06-21 14:35:35 +08:00
|
|
|
), # Toggle power key again when retry.
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_screen_state(ad: android_device.AndroidDevice) -> bool:
|
2025-06-02 06:31:34 +00:00
|
|
|
return get_value_of_key_from_dumpsys(ad, 'power', 'mWakefulness') == 'Awake'
|
2024-06-21 14:35:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_value_of_key_from_dumpsys(
|
|
|
|
|
ad: android_device.AndroidDevice, service: str, key: str
|
|
|
|
|
) -> str:
|
|
|
|
|
output = get_dumpsys_for_service(ad, service)
|
|
|
|
|
# Search for key=value pattern from the dumpsys output.
|
|
|
|
|
# e.g. mWakefulness=Awake
|
2025-06-02 06:31:34 +00:00
|
|
|
pattern = rf'{key}=(.*)'
|
2024-06-21 14:35:35 +08:00
|
|
|
# Only look for the first occurrence.
|
|
|
|
|
match = re.search(pattern, output)
|
|
|
|
|
if match:
|
|
|
|
|
ad.log.debug(
|
2025-06-02 06:31:34 +00:00
|
|
|
'Getting key-value from dumpsys: ' + key + '=' + match.group(1)
|
2024-06-21 14:35:35 +08:00
|
|
|
)
|
|
|
|
|
return match.group(1)
|
|
|
|
|
else:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def expect_dumpsys_state_with_retry(
|
|
|
|
|
ad: android_device.AndroidDevice,
|
|
|
|
|
service: str,
|
|
|
|
|
key: str,
|
|
|
|
|
expected_state: bool,
|
|
|
|
|
retry_interval_sec: int = 1,
|
|
|
|
|
) -> None:
|
|
|
|
|
def predicate():
|
|
|
|
|
value = get_value_of_key_from_dumpsys(ad, service, key)
|
|
|
|
|
if value is None:
|
|
|
|
|
return False
|
|
|
|
|
return value.lower() == str(expected_state).lower()
|
|
|
|
|
|
|
|
|
|
assert_utils.expect_with_retry(
|
|
|
|
|
predicate=predicate,
|
|
|
|
|
retry_interval_sec=retry_interval_sec,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_dumpsys_for_service(
|
|
|
|
|
ad: android_device.AndroidDevice, service: str
|
|
|
|
|
) -> str:
|
2025-06-02 06:31:34 +00:00
|
|
|
return adb_shell(ad, 'dumpsys ' + service)
|
2024-06-21 14:35:35 +08:00
|
|
|
|
|
|
|
|
|
2025-05-28 13:13:17 +00:00
|
|
|
def unroot_adb(self) -> bytes:
|
|
|
|
|
"""Executes the 'adb unroot' command on the device with retry logic.
|
|
|
|
|
|
|
|
|
|
This method is intended to be dynamically attached to an AdbProxy instance
|
|
|
|
|
(or similar object) that provides an '_exec_adb_cmd' method for executing
|
|
|
|
|
ADB commands. It attempts to unroot the device, and if an AdbError occurs,
|
|
|
|
|
it retries the command a specified number of times with an exponentially
|
|
|
|
|
increasing delay.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
self: The instance of the ADB client (e.g., an AdbProxy object) that has the
|
|
|
|
|
'_exec_adb_cmd' method available to run ADB commands.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bytes: The raw byte output from the 'adb unroot' command's successful
|
|
|
|
|
execution.
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
adb.AdbError: If the 'adb unroot' command fails after all specified
|
|
|
|
|
retry attempts.
|
|
|
|
|
"""
|
|
|
|
|
retry_interval = adb.ADB_ROOT_RETRY_ATTEMPT_INTERVAL_SEC
|
|
|
|
|
for attempt in range(adb.ADB_ROOT_RETRY_ATTEMPTS):
|
|
|
|
|
try:
|
|
|
|
|
# Assuming 'self' here is an object that has a '_exec_adb_cmd' method,
|
|
|
|
|
# similar to your original 'root' method's context (e.g., an AdbProxy).
|
|
|
|
|
return self._exec_adb_cmd(
|
|
|
|
|
'unroot', args=None, shell=False, timeout=None, stderr=None
|
|
|
|
|
)
|
|
|
|
|
except adb.AdbError as e:
|
|
|
|
|
if attempt + 1 < adb.ADB_ROOT_RETRY_ATTEMPTS:
|
|
|
|
|
# Decode stderr for logging, as Mobly often provides bytes
|
|
|
|
|
decoded_stderr = e.stderr.decode('utf-8', errors='ignore').strip()
|
|
|
|
|
logging.error(
|
|
|
|
|
f'Retry the command "{utils.cli_cmd_to_string(e.cmd)}" since Error'
|
|
|
|
|
f' "{decoded_stderr}" occurred (attempt'
|
|
|
|
|
f' {attempt + 1}/{adb.ADB_ROOT_RETRY_ATTEMPTS}).'
|
|
|
|
|
)
|
|
|
|
|
time.sleep(retry_interval)
|
|
|
|
|
retry_interval *= 2
|
|
|
|
|
else:
|
|
|
|
|
raise e
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unroot(ad: android_device.AndroidDevice):
|
|
|
|
|
"""Dynamically unroots an Android device by attaching 'unroot_adb' to its ADB proxy.
|
|
|
|
|
|
|
|
|
|
This function addresses a potential issue where Mobly might attempt to root
|
|
|
|
|
a device upon registration. When running user or userdebug builds,
|
|
|
|
|
this automatic rooting can lead to unexpected or inconsistent device behavior.
|
|
|
|
|
To mitigate this, this function temporarily attaches the 'unroot_adb' method
|
|
|
|
|
to the device's ADB proxy (`ad.adb`), executes it, and then removes it,
|
|
|
|
|
ensuring the device returns to an unrooted state and the ADB proxy's state
|
|
|
|
|
remains clean.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
ad: The `android_device.AndroidDevice` instance to be unrooted.
|
|
|
|
|
"""
|
|
|
|
|
attach_method_name = 'unroot_adb'
|
|
|
|
|
try:
|
|
|
|
|
setattr(ad.adb, attach_method_name, types.MethodType(unroot_adb, ad.adb))
|
|
|
|
|
getattr(ad.adb, attach_method_name)()
|
|
|
|
|
ad.adb.wait_for_device()
|
|
|
|
|
finally:
|
|
|
|
|
delattr(ad.adb, attach_method_name)
|
|
|
|
|
|
|
|
|
|
|
2024-06-21 14:35:35 +08:00
|
|
|
def adb_shell(ad: android_device.AndroidDevice, shell_cmd: str) -> str:
|
|
|
|
|
"""Runs adb shell command.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
ad: Android device object.
|
|
|
|
|
shell_cmd: string of list of strings, adb shell command.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
string, replies from adb shell command.
|
|
|
|
|
"""
|
2025-06-02 06:31:34 +00:00
|
|
|
ad.log.debug('Executing adb shell %s', shell_cmd)
|
2024-06-21 14:35:35 +08:00
|
|
|
data = ad.adb.shell(shell_cmd)
|
|
|
|
|
return data.decode(BYTE_DECODE_UTF_8).strip()
|