新增模块覆盖: 系统层 (14): system, security, print, opengl, renderscript, transition, test, service_notification, service_controls, service_quicksettings, service_wallpaper, service_autofill, service_dreams, service_vr 应用层 (13): app_admin, app_backup, app_job, app_usage, app_role, app_people, appwidget, companion, credentials, mtp, sip, telecom, speech 硬件媒体层 (15): hardware_display, hardware_fingerprint, hardware_usb, hardware_input, media_audiofx, media_browse, media_session, media_tv, media_midi, media_projection, uwb, se, view_autofill, view_textclassifier, view_translation 总计 94 个模块,75,342 行代码
527 lines
19 KiB
Python
527 lines
19 KiB
Python
"""
|
|
Android Usage Stats API — UsageStatsManager, UsageStats, UsageEvents, ConfigurationStats, StorageStatsManager
|
|
方法签名完全对齐 Android SDK
|
|
"""
|
|
|
|
import json
|
|
import os as _os
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from typing import Any, Dict, List, Optional, Set, Tuple, Callable
|
|
from collections import defaultdict
|
|
|
|
from .._core import Stub, AndroidOnlyError, resolve_android_path, _app_data_dir, _sdcard_root
|
|
from ..java.lang import RuntimeException
|
|
|
|
|
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
|
|
|
__all__ = [
|
|
"UsageStatsManager",
|
|
"UsageStats",
|
|
"UsageEvents",
|
|
"ConfigurationStats",
|
|
"CacheUsageStats",
|
|
"NetworkUsageStats",
|
|
"UsageStatsManagerInternal",
|
|
"StorageStatsManager",
|
|
"StorageStats",
|
|
]
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# UsageStatsManager
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class UsageStatsManager(Stub):
|
|
"""UsageStatsManager — 使用统计管理器 (桌面模拟)"""
|
|
|
|
INTERVAL_DAILY = 0
|
|
INTERVAL_WEEKLY = 1
|
|
INTERVAL_MONTHLY = 2
|
|
INTERVAL_YEARLY = 3
|
|
INTERVAL_BEST = 4
|
|
INTERVAL_COUNT = 5
|
|
|
|
USAGE_SOURCE_UNSPECIFIED = 0
|
|
USAGE_SOURCE_CURRENT_ACTIVITY = 1
|
|
USAGE_SOURCE_TASK_ROOT_ACTIVITY = 2
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._app_inactive: Dict[str, bool] = {}
|
|
|
|
def queryUsageStats(
|
|
self, intervalType: int, beginTime: int, endTime: int
|
|
) -> List["UsageStats"]:
|
|
"""查询使用统计信息"""
|
|
result: List[UsageStats] = []
|
|
stats_dir = _os.path.join(_app_data_dir, "usagestats")
|
|
if _os.path.exists(stats_dir):
|
|
for fname in _os.listdir(stats_dir):
|
|
if fname.endswith(".json"):
|
|
filepath = _os.path.join(stats_dir, fname)
|
|
try:
|
|
with open(filepath, "r") as f:
|
|
data = json.load(f)
|
|
stats = UsageStats()
|
|
stats._package_name = data.get("package", fname[:-5])
|
|
stats._first_time = data.get("first_time", beginTime)
|
|
stats._last_time = data.get("last_time", endTime)
|
|
stats._last_used = data.get("last_used", beginTime)
|
|
stats._total_foreground = data.get("total_foreground", 0)
|
|
stats._launch_count = data.get("launch_count", 0)
|
|
if stats._last_time >= beginTime and stats._last_time <= endTime:
|
|
result.append(stats)
|
|
except (json.JSONDecodeError, IOError):
|
|
pass
|
|
return result
|
|
|
|
def queryConfigurations(
|
|
self, intervalType: int, beginTime: int, endTime: int
|
|
) -> List["ConfigurationStats"]:
|
|
"""查询配置统计"""
|
|
return []
|
|
|
|
def queryEvents(
|
|
self, beginTime: int, endTime: int
|
|
) -> "UsageEvents":
|
|
"""查询使用事件"""
|
|
return UsageEvents()
|
|
|
|
def isAppInactive(self, packageName: str) -> bool:
|
|
"""检查应用是否处于非活跃状态"""
|
|
return self._app_inactive.get(packageName, False)
|
|
|
|
def setAppInactive(self, packageName: str, inactive: bool):
|
|
"""设置应用非活跃状态"""
|
|
self._app_inactive[packageName] = inactive
|
|
|
|
def onCarrierPrivilegedAppsChanged(self):
|
|
"""运营商特权应用变更 (桌面存根)"""
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"UsageStatsManager@{hex(id(self))}"
|
|
|
|
def equals(self, other) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# UsageStats
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class UsageStats(Stub):
|
|
"""UsageStats — 应用使用统计"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._package_name = ""
|
|
self._first_time = 0
|
|
self._last_time = 0
|
|
self._last_used = 0
|
|
self._total_foreground = 0
|
|
self._launch_count = 0
|
|
|
|
def getPackageName(self) -> str:
|
|
return self._package_name
|
|
|
|
def getFirstTimeStamp(self) -> int:
|
|
return self._first_time
|
|
|
|
def getLastTimeStamp(self) -> int:
|
|
return self._last_time
|
|
|
|
def getLastUsedTime(self) -> int:
|
|
return self._last_used
|
|
|
|
def getTotalTimeInForeground(self) -> int:
|
|
return self._total_foreground
|
|
|
|
def getAppLaunchCount(self) -> int:
|
|
return self._launch_count
|
|
|
|
def toString(self) -> str:
|
|
return (f"UsageStats(pkg={self._package_name}, "
|
|
f"foreground={self._total_foreground}ms, "
|
|
f"launches={self._launch_count})")
|
|
|
|
def equals(self, other) -> bool:
|
|
if not isinstance(other, UsageStats):
|
|
return False
|
|
return self._package_name == other._package_name
|
|
|
|
def hashCode(self) -> int:
|
|
return hash(self._package_name) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# UsageEvents
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class UsageEvents(Stub):
|
|
"""UsageEvents — 使用事件列表"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._events: List["UsageEvents.Event"] = []
|
|
self._index = 0
|
|
|
|
def getNextEvent(self, eventOut: "UsageEvents.Event") -> bool:
|
|
"""获取下一个事件"""
|
|
if self._index < len(self._events):
|
|
ev = self._events[self._index]
|
|
eventOut._package_name = ev._package_name
|
|
eventOut._class_name = ev._class_name
|
|
eventOut._action = ev._action
|
|
eventOut._time_stamp = ev._time_stamp
|
|
self._index += 1
|
|
return True
|
|
return False
|
|
|
|
def hasNextEvent(self) -> bool:
|
|
"""检查是否还有下一个事件"""
|
|
return self._index < len(self._events)
|
|
|
|
def resetToStart(self):
|
|
"""重置到起始位置"""
|
|
self._index = 0
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# Event — 内部事件类
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
class Event(Stub):
|
|
"""UsageEvents.Event — 使用事件"""
|
|
|
|
# --- Event action constants ---
|
|
MOVE_TO_FOREGROUND = 1
|
|
MOVE_TO_BACKGROUND = 2
|
|
CONFIGURATION_CHANGE = 3
|
|
SHORTCUT_INVOCATION = 4
|
|
STANDBY_BUDGET_CHANGED = 5
|
|
USER_INTERACTION = 6
|
|
NOTIFICATION_SEEN = 7
|
|
NOTIFICATION_INTERRUPTION = 8
|
|
CHOOSER_ACTION = 9
|
|
APP_COMPONENT_USED = 10
|
|
ACTIVITY_STOPPED = 11
|
|
SYSTEM_INTERACTION = 12
|
|
FLAG_IS_PACKAGE_INSTALL = 13
|
|
FLAG_NO_INTERRUPTION = 14
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._package_name = ""
|
|
self._class_name = ""
|
|
self._action = 0
|
|
self._time_stamp = 0
|
|
|
|
def getPackageName(self) -> str:
|
|
return self._package_name
|
|
|
|
def getClassName(self) -> str:
|
|
return self._class_name
|
|
|
|
def getAction(self) -> int:
|
|
return self._action
|
|
|
|
def getTimeStamp(self) -> int:
|
|
return self._time_stamp
|
|
|
|
def toString(self) -> str:
|
|
return (f"Event(pkg={self._package_name}, "
|
|
f"action={self._action}, time={self._time_stamp})")
|
|
|
|
def equals(self, other) -> bool:
|
|
if not isinstance(other, UsageEvents.Event):
|
|
return False
|
|
return (self._package_name == other._package_name
|
|
and self._action == other._action
|
|
and self._time_stamp == other._time_stamp)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash((self._package_name, self._action, self._time_stamp)) & 0x7FFFFFFF
|
|
|
|
def toString(self) -> str:
|
|
return f"UsageEvents(count={len(self._events)})"
|
|
|
|
def equals(self, other) -> bool:
|
|
if not isinstance(other, UsageEvents):
|
|
return False
|
|
return self._events == other._events
|
|
|
|
def hashCode(self) -> int:
|
|
return hash(len(self._events)) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# ConfigurationStats
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class ConfigurationStats(Stub):
|
|
"""ConfigurationStats — 配置统计"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._first_time = 0
|
|
self._last_time = 0
|
|
self._last_active = 0
|
|
self._total_active = 0
|
|
self._activation_count = 0
|
|
|
|
def getFirstTimeStamp(self) -> int:
|
|
return self._first_time
|
|
|
|
def getLastTimeStamp(self) -> int:
|
|
return self._last_time
|
|
|
|
def getLastTimeActive(self) -> int:
|
|
return self._last_active
|
|
|
|
def getTotalTimeActive(self) -> int:
|
|
return self._total_active
|
|
|
|
def getActivationCount(self) -> int:
|
|
return self._activation_count
|
|
|
|
def toString(self) -> str:
|
|
return f"ConfigurationStats(active={self._total_active}ms, count={self._activation_count})"
|
|
|
|
def equals(self, other) -> bool:
|
|
if not isinstance(other, ConfigurationStats):
|
|
return False
|
|
return (self._first_time == other._first_time
|
|
and self._last_time == other._last_time)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash((self._first_time, self._last_time)) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# CacheUsageStats
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class CacheUsageStats(Stub):
|
|
"""CacheUsageStats — 缓存使用统计"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._cache_size = 0
|
|
self._total_size = 0
|
|
|
|
def getCacheSize(self) -> int:
|
|
return self._cache_size
|
|
|
|
def getTotalSize(self) -> int:
|
|
return self._total_size
|
|
|
|
def toString(self) -> str:
|
|
return f"CacheUsageStats(cache={self._cache_size}B)"
|
|
|
|
def equals(self, other) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# NetworkUsageStats
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class NetworkUsageStats(Stub):
|
|
"""NetworkUsageStats — 网络使用统计"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._rx_bytes = 0
|
|
self._tx_bytes = 0
|
|
self._rx_packets = 0
|
|
self._tx_packets = 0
|
|
|
|
def getRxBytes(self) -> int:
|
|
return self._rx_bytes
|
|
|
|
def getTxBytes(self) -> int:
|
|
return self._tx_bytes
|
|
|
|
def getRxPackets(self) -> int:
|
|
return self._rx_packets
|
|
|
|
def getTxPackets(self) -> int:
|
|
return self._tx_packets
|
|
|
|
def toString(self) -> str:
|
|
return f"NetworkUsageStats(rx={self._rx_bytes}B, tx={self._tx_bytes}B)"
|
|
|
|
def equals(self, other) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# UsageStatsManagerInternal
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class UsageStatsManagerInternal(Stub):
|
|
"""UsageStatsManagerInternal — 内部使用统计管理器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def reportEvent(self, event: Any):
|
|
"""报告使用事件"""
|
|
pass
|
|
|
|
def toString(self) -> str:
|
|
return f"UsageStatsManagerInternal@{hex(id(self))}"
|
|
|
|
def equals(self, other) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# StorageStatsManager
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class StorageStatsManager(Stub):
|
|
"""StorageStatsManager — 存储统计管理器"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def queryStatsForPackage(
|
|
self, storageUuid: Any, packageName: str, userHandle: Any
|
|
) -> "StorageStats":
|
|
"""查询指定应用存储统计"""
|
|
return self._query_stats(storageUuid, packageName)
|
|
|
|
def queryStatsForUid(
|
|
self, storageUuid: Any, uid: int
|
|
) -> "StorageStats":
|
|
"""查询指定 UID 存储统计"""
|
|
return self._query_stats(storageUuid, f"uid_{uid}")
|
|
|
|
def queryStatsForUser(
|
|
self, storageUuid: Any, userHandle: Any
|
|
) -> "StorageStats":
|
|
"""查询指定用户存储统计"""
|
|
return self._query_stats(storageUuid, f"user_{userHandle}")
|
|
|
|
def queryExternalStatsForUser(
|
|
self, storageUuid: Any, userHandle: Any
|
|
) -> "StorageStats":
|
|
"""查询外部用户存储统计"""
|
|
return self._query_stats(storageUuid, f"external_user_{userHandle}")
|
|
|
|
def getFreeBytes(self, storageUuid: Any) -> int:
|
|
"""获取空闲字节数"""
|
|
try:
|
|
st = _os.statvfs(_sdcard_root)
|
|
return st.f_bavail * st.f_frsize
|
|
except (OSError, AttributeError):
|
|
return 1024 * 1024 * 1024 # 默认 1GB
|
|
|
|
def getTotalBytes(self, storageUuid: Any) -> int:
|
|
"""获取总字节数"""
|
|
try:
|
|
st = _os.statvfs(_sdcard_root)
|
|
return st.f_blocks * st.f_frsize
|
|
except (OSError, AttributeError):
|
|
return 64 * 1024 * 1024 * 1024 # 默认 64GB
|
|
|
|
def _query_stats(self, storage_uuid: Any, tag: str) -> "StorageStats":
|
|
"""内部查询存储统计"""
|
|
stats = StorageStats()
|
|
pkg_dir = _os.path.join(_app_data_dir, "files")
|
|
if _os.path.exists(pkg_dir):
|
|
for dirpath, dirnames, filenames in _os.walk(pkg_dir):
|
|
for fname in filenames:
|
|
fpath = _os.path.join(dirpath, fname)
|
|
try:
|
|
fsize = _os.path.getsize(fpath)
|
|
ext = _os.path.splitext(fname)[1].lower()
|
|
if ext in (".jpg", ".png", ".gif", ".webp", ".bmp"):
|
|
stats._image_bytes += fsize
|
|
elif ext in (".mp3", ".wav", ".flac", ".ogg", ".aac"):
|
|
stats._music_bytes += fsize
|
|
elif ext in (".mp4", ".avi", ".mkv", ".mov", ".webm"):
|
|
stats._video_bytes += fsize
|
|
else:
|
|
stats._data_bytes += fsize
|
|
except OSError:
|
|
pass
|
|
return stats
|
|
|
|
def toString(self) -> str:
|
|
return f"StorageStatsManager@{hex(id(self))}"
|
|
|
|
def equals(self, other) -> bool:
|
|
return self is other
|
|
|
|
def hashCode(self) -> int:
|
|
return id(self) & 0x7FFFFFFF
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# StorageStats
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
class StorageStats(Stub):
|
|
"""StorageStats — 存储统计"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._app_bytes = 0
|
|
self._data_bytes = 0
|
|
self._cache_bytes = 0
|
|
self._music_bytes = 0
|
|
self._video_bytes = 0
|
|
self._image_bytes = 0
|
|
self._game_bytes = 0
|
|
|
|
def getAppBytes(self) -> int:
|
|
return self._app_bytes
|
|
|
|
def getDataBytes(self) -> int:
|
|
return self._data_bytes
|
|
|
|
def getCacheBytes(self) -> int:
|
|
return self._cache_bytes
|
|
|
|
def getMusicBytes(self) -> int:
|
|
return self._music_bytes
|
|
|
|
def getVideoBytes(self) -> int:
|
|
return self._video_bytes
|
|
|
|
def getImageBytes(self) -> int:
|
|
return self._image_bytes
|
|
|
|
def getGameBytes(self) -> int:
|
|
return self._game_bytes
|
|
|
|
def toString(self) -> str:
|
|
return (f"StorageStats(app={self._app_bytes}B, data={self._data_bytes}B, "
|
|
f"cache={self._cache_bytes}B, music={self._music_bytes}B, "
|
|
f"video={self._video_bytes}B, image={self._image_bytes}B)")
|
|
|
|
def equals(self, other) -> bool:
|
|
if not isinstance(other, StorageStats):
|
|
return False
|
|
return (self._app_bytes == other._app_bytes
|
|
and self._data_bytes == other._data_bytes)
|
|
|
|
def hashCode(self) -> int:
|
|
return hash((self._app_bytes, self._data_bytes)) & 0x7FFFFFFF |