""" Android Wallpaper API — WallpaperManager, WallpaperInfo, WallpaperService, WallpaperService.Engine 方法签名完全对齐 Android SDK """ from __future__ import annotations import os import time import tempfile from typing import Any, Dict, List, Optional, Tuple, Callable, BinaryIO from .._core import Stub, AndroidOnlyError, resolve_android_path, _sdcard_root, _app_data_dir from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) # ═══════════════════════════════════════════════════════════════ # 常量 # ═══════════════════════════════════════════════════════════════ # FLAG_* 常量 FLAG_SYSTEM = 0x00000001 FLAG_LOCK = 0x00000002 FLAG_SYSTEM_AND_LOCK = FLAG_SYSTEM | FLAG_LOCK FLAG_SET_BOTH = FLAG_SYSTEM | FLAG_LOCK # ACTION_* 常量 ACTION_CHANGE_LIVE_WALLPAPER = "android.service.wallpaper.CHANGE_LIVE_WALLPAPER" ACTION_LIVE_WALLPAPER_CHOOSER = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER" ACTION_CROP_AND_SET_WALLPAPER = "android.service.wallpaper.CROP_AND_SET_WALLPAPER" ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED" ACTION_SET_WALLPAPER = "android.service.wallpaper.SET_WALLPAPER" # WALLPAPER_TYPE 常量 WALLPAPER_TYPE_SYSTEM = 1 WALLPAPER_TYPE_LOCK = 2 WALLPAPER_TYPE_BOTH = 3 WALLPAPER_TYPE_LIVE = 4 # 其它常量 WALLPAPER_HEIGHT = 1920 WALLPAPER_WIDTH = 1080 MIN_CROP_WIDTH = 320 MIN_CROP_HEIGHT = 480 # ═══════════════════════════════════════════════════════════════ # WallpaperManager # ═══════════════════════════════════════════════════════════════ class WallpaperManager(Stub): """WallpaperManager — 壁纸管理器 (桌面存根)""" ACTION_CHANGE_LIVE_WALLPAPER = ACTION_CHANGE_LIVE_WALLPAPER ACTION_LIVE_WALLPAPER_CHOOSER = ACTION_LIVE_WALLPAPER_CHOOSER ACTION_CROP_AND_SET_WALLPAPER = ACTION_CROP_AND_SET_WALLPAPER ACTION_WALLPAPER_CHANGED = ACTION_WALLPAPER_CHANGED ACTION_SET_WALLPAPER = ACTION_SET_WALLPAPER FLAG_SYSTEM = FLAG_SYSTEM FLAG_LOCK = FLAG_LOCK FLAG_SYSTEM_AND_LOCK = FLAG_SYSTEM_AND_LOCK WALLPAPER_TYPE_SYSTEM = WALLPAPER_TYPE_SYSTEM WALLPAPER_TYPE_LOCK = WALLPAPER_TYPE_LOCK WALLPAPER_TYPE_BOTH = WALLPAPER_TYPE_BOTH WALLPAPER_TYPE_LIVE = WALLPAPER_TYPE_LIVE _instance: Optional["WallpaperManager"] = None def __init__(self, context: Any = None): super().__init__() self._context = context self._wallpaper_dir = os.path.join(_app_data_dir, "wallpapers") os.makedirs(self._wallpaper_dir, exist_ok=True) self._system_wallpaper_path = os.path.join(self._wallpaper_dir, "system_wallpaper") self._lock_wallpaper_path = os.path.join(self._wallpaper_dir, "lock_wallpaper") self._wallpaper_info: Optional["WallpaperInfo"] = None self._is_wallpaper_supported = True self._wallpaper_offset = 0.5 self._wallpaper_offsets = (0.0, 0.0, 0.0, 0.0) self._wallpaper_dimensions = (WALLPAPER_WIDTH, WALLPAPER_HEIGHT) self._wallpaper_zoom = 1.0 self._suggest_wallpaper_dimensions = (WALLPAPER_WIDTH, WALLPAPER_HEIGHT) self._crop_hint = (0, 0, WALLPAPER_WIDTH, WALLPAPER_HEIGHT) self._padding = (0, 0, 0, 0) self._pre_wallpaper_components: List[Any] = [] self._wallpaper_visibility = True self._wallpaper_callbacks: List[Callable] = [] self._wallpaper_parallax_effect = True self._wallpaper_dimming = 0.0 @staticmethod def getInstance(context: Any) -> "WallpaperManager": if WallpaperManager._instance is None: WallpaperManager._instance = WallpaperManager(context) return WallpaperManager._instance def getDrawable(self) -> Optional[Any]: """获取当前壁纸 Drawable""" if os.path.exists(self._system_wallpaper_path): from .._core import HAS_PIL if HAS_PIL: try: from PIL import Image as _PILImage return _PILImage.open(self._system_wallpaper_path) except Exception: pass return None def peekDrawable(self) -> Optional[Any]: """获取当前壁纸但不触发加载""" return self.getDrawable() def getFastDrawable(self) -> Optional[Any]: """快速获取壁纸""" return self.getDrawable() def peekFastDrawable(self) -> Optional[Any]: return self.peekDrawable() def getBitmap(self) -> Optional[Any]: """获取壁纸位图""" if os.path.exists(self._system_wallpaper_path): try: from PIL import Image as _PILImage return _PILImage.open(self._system_wallpaper_path) except ImportError: pass return None def peekBitmap(self) -> Optional[Any]: return self.getBitmap() def setBitmap(self, bitmap: Any) -> None: """设置壁纸位图""" self.setBitmap(bitmap, None, True, FLAG_SYSTEM) def setBitmap(self, bitmap: Any, hint: Any, allowBackup: bool = True, which: int = FLAG_SYSTEM) -> None: path = self._get_wallpaper_path(which) try: if hasattr(bitmap, "save"): bitmap.save(path) elif hasattr(bitmap, "tobytes"): with open(path, "wb") as f: f.write(bitmap.tobytes()) else: with open(path, "w") as f: f.write(str(bitmap)) except Exception as e: raise RuntimeException(f"Failed to set wallpaper: {e}") def setStream(self, data: BinaryIO) -> None: self.setStream(data, None, True, FLAG_SYSTEM) def setStream(self, data: BinaryIO, hint: Any, allowBackup: bool = True, which: int = FLAG_SYSTEM) -> None: path = self._get_wallpaper_path(which) with open(path, "wb") as f: f.write(data.read()) def getResourceContext(self) -> Optional[Any]: return self._context def getWallpaperInfo(self) -> Optional["WallpaperInfo"]: return self._wallpaper_info def getWallpaperFile(self, which: int = FLAG_SYSTEM) -> Optional[Any]: path = self._get_wallpaper_path(which) if os.path.exists(path): return open(path, "rb") return None def getWallpaperId(self, which: int = FLAG_SYSTEM) -> int: path = self._get_wallpaper_path(which) if os.path.exists(path): return int(os.path.getmtime(path)) return -1 def hasResourceWallpaper(self, resid: int) -> bool: return False def suggestDesiredDimensions(self, minimumWidth: int, minimumHeight: int) -> None: self._suggest_wallpaper_dimensions = (minimumWidth, minimumHeight) def getDesiredMinimumWidth(self) -> int: return self._suggest_wallpaper_dimensions[0] def getDesiredMinimumHeight(self) -> int: return self._suggest_wallpaper_dimensions[1] def clearWallpaper(self) -> None: self.clear(FLAG_SYSTEM) def clear(self, which: int = FLAG_SYSTEM) -> None: if which & FLAG_SYSTEM: if os.path.exists(self._system_wallpaper_path): os.remove(self._system_wallpaper_path) if which & FLAG_LOCK: if os.path.exists(self._lock_wallpaper_path): os.remove(self._lock_wallpaper_path) def setWallpaperComponent(self, component: Any) -> None: pass def sendWallpaperCommand(self, window: Any, action: str, x: int, y: int, z: int, extras: Any) -> None: pass def setWallpaperOffsetSteps(self, xStep: float, yStep: float) -> None: pass def setWallpaperOffsets(self, window: Any, xOffset: float, yOffset: float) -> None: self._wallpaper_offset = xOffset self._wallpaper_offsets = (xOffset, yOffset, xOffset, yOffset) def getWallpaperOffsets(self, window: Any) -> Tuple[float, float, float, float]: return self._wallpaper_offsets def computeScrollRange(self, window: Any) -> int: return WALLPAPER_WIDTH def computeScrollXRange(self, window: Any) -> int: return WALLPAPER_WIDTH def computeScrollYRange(self, window: Any) -> int: return WALLPAPER_HEIGHT def isWallpaperSupported(self) -> bool: return self._is_wallpaper_supported def isSetWallpaperAllowed(self) -> bool: return True def isWallpaperBackupEligible(self, which: int = FLAG_SYSTEM) -> bool: return True def getWallpaperDimAmount(self) -> float: return self._wallpaper_dimming def setWallpaperDimAmount(self, dimAmount: float) -> None: self._wallpaper_dimming = max(0.0, min(1.0, dimAmount)) def setWallpaperZoomOut(self, zoom: float) -> None: self._wallpaper_zoom = max(0.1, min(1.0, zoom)) def getWallpaperZoomOut(self) -> float: return self._wallpaper_zoom def getCropHint(self) -> Tuple[int, int, int, int]: return self._crop_hint def setCropHint(self, cropHint: Tuple[int, int, int, int]) -> None: self._crop_hint = cropHint def getPadding(self) -> Tuple[int, int, int, int]: return self._padding def isParallaxEnabled(self) -> bool: return self._wallpaper_parallax_effect def setParallaxEnabled(self, enabled: bool) -> None: self._wallpaper_parallax_effect = enabled def addOnColorsChangedListener(self, listener: Any, handler: Any = None) -> None: if listener not in self._wallpaper_callbacks: self._wallpaper_callbacks.append(listener) def removeOnColorsChangedListener(self, listener: Any) -> None: if listener in self._wallpaper_callbacks: self._wallpaper_callbacks.remove(listener) def _get_wallpaper_path(self, which: int) -> str: if which & FLAG_SYSTEM: return self._system_wallpaper_path if which & FLAG_LOCK: return self._lock_wallpaper_path return self._system_wallpaper_path def toString(self) -> str: return ( f"WallpaperManager(system_exists={os.path.exists(self._system_wallpaper_path)}, " f"lock_exists={os.path.exists(self._lock_wallpaper_path)})" ) # ═══════════════════════════════════════════════════════════════ # WallpaperInfo # ═══════════════════════════════════════════════════════════════ class WallpaperInfo(Stub): """WallpaperInfo — 壁纸信息""" def __init__(self, context: Any = None, component: Any = None): super().__init__() self._context = context self._component = component self._package_name: Optional[str] = None self._service_name: Optional[str] = None self._label: Optional[str] = None self._description: Optional[str] = None self._author: Optional[str] = None self._context_description: Optional[str] = None self._settings_activity: Optional[str] = None self._settings_piece: Optional[str] = None self._thumbnail: Optional[Any] = None self._system_wallpaper = False self._supports_ambient_mode = False self._is_standalone = False self._show_metadata_in_preview = True self._shared_library = "" self._wallpaper_author: Optional[str] = None self._wallpaper_description: Optional[str] = None self._wallpaper_context_description: Optional[str] = None self._wallpaper_settings_activity: Optional[str] = None self._wallpaper_thumbnail: Optional[Any] = None self._wallpaper_show_metadata = True self._wallpaper_supports_ambient = False def getPackageName(self) -> Optional[str]: return self._package_name def getServiceName(self) -> Optional[str]: return self._service_name def getServiceInfo(self) -> Optional[Any]: return None def getComponent(self) -> Optional[Any]: return self._component def loadLabel(self, pm: Any) -> str: return self._label or "Wallpaper" def loadDescription(self, pm: Any) -> str: return self._description or "" def loadAuthor(self, pm: Any) -> str: return self._author or "" def loadContextDescription(self, pm: Any) -> str: return self._context_description or "" def loadIcon(self, pm: Any) -> Optional[Any]: return None def loadThumbnail(self, pm: Any) -> Optional[Any]: return self._thumbnail def getSettingsActivity(self) -> Optional[str]: return self._settings_activity def getSettingsSliceUri(self) -> Optional[str]: return None def getSharedLibrary(self) -> str: return self._shared_library def getShowMetadataInPreview(self) -> bool: return self._show_metadata_in_preview def isSystemWallpaper(self) -> bool: return self._system_wallpaper def supportsAmbientMode(self) -> bool: return self._supports_ambient_mode def toString(self) -> str: return f"WallpaperInfo(package={self._package_name}, service={self._service_name})" # ═══════════════════════════════════════════════════════════════ # WallpaperService # ═══════════════════════════════════════════════════════════════ class WallpaperService(Stub): """WallpaperService — 动态壁纸服务基类""" SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService" SERVICE_META_DATA = "android.service.wallpaper" WALLPAPER_HEIGHT = WALLPAPER_HEIGHT WALLPAPER_WIDTH = WALLPAPER_WIDTH def __init__(self): super().__init__() self._engine: Optional["WallpaperService.Engine"] = None self._created = False self._wallpaper_connected = False def onCreate(self) -> None: self._created = True def onDestroy(self) -> None: self._created = False self._wallpaper_connected = False def onCreateEngine(self) -> Optional["WallpaperService.Engine"]: return None def onCreateSurfaceHolder(self) -> Optional[Any]: return None def getWallpaperEngine(self) -> Optional["WallpaperService.Engine"]: return self._engine def toString(self) -> str: return f"WallpaperService(created={self._created})" # ═══════════════════════════════════════════════════════════ # WallpaperService.Engine # ═══════════════════════════════════════════════════════════ class Engine(Stub): """WallpaperService.Engine — 动态壁纸引擎""" def __init__(self, service: "WallpaperService"): super().__init__() self._service = service self._is_visible = False self._is_preview = False self._surface_holder: Optional[Any] = None self._surface_created = False self._surface_changed = False self._surface_destroyed = False self._offsets_changed = False self._desired_width = WALLPAPER_WIDTH self._desired_height = WALLPAPER_HEIGHT self._x_offset = 0.5 self._y_offset = 0.5 self._x_step = 0.0 self._y_step = 0.0 self._visible = True self._touch_events_enabled = True self._mSurfaceCreated = False self._mSurfaceValid = False self._mDrawingAllowed = False self._mOffsetMessageEnqueued = False self._mZoom = 1.0 self._mZoomOut = 1.0 self._mOffsetRequired = False self._mOffsetsChanged = False self._mSurfaceChanged = False self._mCreated = True self._mSurfaceDestroyed = False def getSurfaceHolder(self) -> Optional[Any]: return self._surface_holder def isVisible(self) -> bool: return self._is_visible def isPreview(self) -> bool: return self._is_preview def setTouchEventsEnabled(self, enabled: bool) -> None: self._touch_events_enabled = enabled def getTouchEventsEnabled(self) -> bool: return self._touch_events_enabled def setOffsetNotificationsEnabled(self, enabled: bool) -> None: pass def onCreate(self, surfaceHolder: Any) -> None: self._surface_holder = surfaceHolder self._surface_created = True def onDestroy(self) -> None: self._surface_destroyed = True def onVisibilityChanged(self, visible: bool) -> None: self._is_visible = visible self._visible = visible def onTouchEvent(self, event: Any) -> None: pass def onSurfaceChanged(self, holder: Any, format: int, width: int, height: int) -> None: self._desired_width = width self._desired_height = height self._surface_changed = True def onSurfaceCreated(self, holder: Any) -> None: self._surface_created = True self._mSurfaceCreated = True self._mSurfaceValid = True def onSurfaceDestroyed(self, holder: Any) -> None: self._surface_destroyed = True self._mSurfaceDestroyed = True self._mSurfaceValid = False def onSurfaceRedrawNeeded(self, holder: Any) -> None: pass def onOffsetsChanged( self, xOffset: float, yOffset: float, xOffsetStep: float, yOffsetStep: float, xPixelOffset: int, yPixelOffset: int, ) -> None: self._x_offset = xOffset self._y_offset = yOffset self._x_step = xOffsetStep self._y_step = yOffsetStep def onDesiredSizeChanged(self, desiredWidth: int, desiredHeight: int) -> None: self._desired_width = desiredWidth self._desired_height = desiredHeight def onZoomChanged(self, zoom: float) -> None: self._mZoom = zoom def setZoomOut(self, zoom: float) -> None: self._mZoomOut = zoom def getZoomOut(self) -> float: return self._mZoomOut def notifyZoomChanged(self) -> None: pass def notifyColorsChanged(self) -> None: pass def getDesiredMinimumWidth(self) -> int: return self._desired_width def getDesiredMinimumHeight(self) -> int: return self._desired_height def getXOffset(self) -> float: return self._x_offset def getYOffset(self) -> float: return self._y_offset def getXStep(self) -> float: return self._x_step def getYStep(self) -> float: return self._y_step def isCreated(self) -> bool: return self._mCreated def isSurfaceCreated(self) -> bool: return self._mSurfaceCreated def isSurfaceValid(self) -> bool: return self._mSurfaceValid def isVisible(self) -> bool: return self._visible def getSurfaceHolder(self) -> Optional[Any]: return self._surface_holder def getService(self) -> "WallpaperService": return self._service def toString(self) -> str: return ( f"WallpaperService.Engine(visible={self._visible}, " f"surface_created={self._mSurfaceCreated}, " f"zoom={self._mZoom})" )