feat: 深度完善 — 存根转真实实现、UI类方法补全、Java集合/IO增强
存根转真实实现: - ContentResolver: query/insert/update/delete对file://URI真实文件操作, getType用mimetypes - Context: getResources/getAssets/getString/getColor 委托真实Resources - Bitmap.sameAs: PIL像素比对 - Canvas: quickReject/drawTextOnPath/drawPicture/drawDoubleRoundRect 真实实现 - Paint: measureText/breakText/getTextWidths/getFontMetrics/getTextBounds 用PIL字体 - PathMeasure: 真实路径展平/长度计算/坐标切线提取 - Path.op/Region.op: 布尔运算 - Camera: 3D变换矩阵计算 - SystemClock.setCurrentTimeMillis: date -s命令 - Environment.isExternalStorageRemovable: /proc/mounts检查 - Process.myUid: os.getuid() - PowerManager.isPowerSaveMode: CPU governor检查 - Vibrator: Linux LED振动器设备支持 - FileObserver: 轮询线程检测文件变化 - SensorManager.getRotationMatrix: 完整旋转矩阵算法 - BatteryManager: LinuxPower真实电池数据 - Geocoder: Nominatim API逆地理编码 - LocationManager: addProximityAlert/setTestProviderLocation UI类方法补全: - TextView: setPaddingRelative/getTextColor/setBreakStrategy/setJustificationMode - ImageView: setColorFilter/getColorFilter/clearColorFilter - CheckBox/Switch: setOnCheckedChangeListener - ListView: clearChoices - ViewGroup: setLayoutTransition/setTouchscreenBlocksFocus/getChildVisibleRect - MotionEvent: getHistoricalX/Y/Pressure/EventTime/addBatch/getEdgeFlags/setAction Java集合增强: - ArrayList: spliterator/stream - HashSet: spliterator/stream/removeIf - TreeMap: replaceAll/compute/computeIfAbsent/computeIfPresent/merge - LinkedHashMap: clone (保留access_order) - PriorityQueue: spliterator/clone Java IO增强: - BufferedWriter: append/check - PrintWriter: append - ByteArrayOutputStream: writeTo - ByteArrayInputStream: markSupported - BufferedInputStream: available/mark/reset/markSupported + 修复_fill缓冲区填充bug - SequenceInputStream: 补全实现(Enumeration/多流/跨流聚合读取) 修复: - ViewGroup.addView: 合并重复定义为智能参数分派 - view_ext.ViewGroup: 继承view.ViewGroup - view_ext.KeyEvent: 继承view.KeyEvent - hardware_sensor_ext.SensorManager: 继承hardware.SensorManager - app_fragment: 使用java.lang.IllegalStateException - content_intent.Intent: setData/getScheme正确从Uri提取scheme - __init__.py: 补全Fragment/KeyEvent/MotionEvent/TrafficStats/RecyclerView/TreeMap/Vibrator等导出 统计: 221模块, 3662类, 30952方法, 182302行
This commit is contained in:
1 parent
39b1da37c9
commit
4235f8692c
13 files changed
+2248
-163
No files matched your search
@@ -4,6 +4,7 @@ Android Content 组件 — Context, ContentResolver, Uri, SharedPreferences 等
|
||||
"""
|
||||
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
@@ -372,34 +373,134 @@ class ContentResolver(Stub):
|
||||
super().__init__()
|
||||
self._observers: Dict[str, List[Any]] = {}
|
||||
|
||||
@staticmethod
|
||||
def _uri_to_file_path(uri) -> Optional[str]:
|
||||
"""将 file:// 或纯路径 Uri 转换为本地文件系统路径"""
|
||||
try:
|
||||
uri_str = str(uri)
|
||||
except Exception:
|
||||
return None
|
||||
# 处理字符串输入
|
||||
if isinstance(uri, str):
|
||||
if uri.startswith("file://"):
|
||||
return uri[7:]
|
||||
if "://" not in uri:
|
||||
return uri if uri else None
|
||||
return None
|
||||
scheme = getattr(uri, "_scheme", "") or ""
|
||||
path = getattr(uri, "_path", "") or ""
|
||||
if scheme == "file":
|
||||
return path if path else None
|
||||
if scheme == "" and path:
|
||||
return path
|
||||
return None
|
||||
|
||||
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
||||
selection_args: Optional[List[str]], sort_order: Optional[str],
|
||||
cancellation_signal: Any = None) -> Any:
|
||||
"""Query content provider"""
|
||||
sys.stderr.write(f"[ContentResolver] query: uri={uri}\n")
|
||||
"""Query content provider. 对 file:// URI 返回基于目录列表的 Cursor。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path is not None and os.path.isdir(path):
|
||||
try:
|
||||
from .database import Cursor, MatrixCursor
|
||||
cols = projection or ["_id", "name", "size", "last_modified"]
|
||||
cursor = MatrixCursor(cols)
|
||||
for idx, name in enumerate(sorted(os.listdir(path))):
|
||||
full = os.path.join(path, name)
|
||||
try:
|
||||
st = os.stat(full)
|
||||
size = st.st_size
|
||||
mtime = int(st.st_mtime * 1000)
|
||||
except OSError:
|
||||
size = 0
|
||||
mtime = 0
|
||||
cursor.addRow([idx, name, size, mtime])
|
||||
return cursor
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
||||
sys.stderr.write(f"[ContentResolver] insert: uri={uri}\n")
|
||||
"""Insert. 对 file:// URI 将 ContentValues 写为 JSON 文件。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path is not None and isinstance(values, ContentValues):
|
||||
try:
|
||||
os.makedirs(path if os.path.isdir(path) else os.path.dirname(path) or ".", exist_ok=True)
|
||||
target = path if not os.path.isdir(path) else os.path.join(path, "insert.json")
|
||||
data = values._values if hasattr(values, "_values") else {}
|
||||
with open(target, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, default=str)
|
||||
return Uri.withAppendedPath(uri, os.path.basename(target))
|
||||
except OSError:
|
||||
pass
|
||||
return uri
|
||||
|
||||
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
||||
selection_args: Optional[List[str]]) -> int:
|
||||
sys.stderr.write(f"[ContentResolver] update: uri={uri}\n")
|
||||
"""Update. 对 file:// URI 更新对应 JSON 文件。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path is not None and isinstance(values, ContentValues) and os.path.isfile(path):
|
||||
try:
|
||||
data = values._values if hasattr(values, "_values") else {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
existing = json.load(f)
|
||||
if isinstance(existing, dict):
|
||||
existing.update(data)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(existing, f, ensure_ascii=False, default=str)
|
||||
return 1
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
return 0
|
||||
|
||||
def delete(self, uri: Uri, selection: Optional[str],
|
||||
selection_args: Optional[List[str]]) -> int:
|
||||
sys.stderr.write(f"[ContentResolver] delete: uri={uri}\n")
|
||||
"""Delete. 对 file:// URI 删除对应文件或目录。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path is not None:
|
||||
try:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
return 1
|
||||
if os.path.isdir(path):
|
||||
import shutil
|
||||
shutil.rmtree(path)
|
||||
return 1
|
||||
except OSError:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def applyBatch(self, authority: str, operations: List[Any]) -> List[Any]:
|
||||
sys.stderr.write(f"[ContentResolver] applyBatch: authority={authority}\n")
|
||||
return []
|
||||
"""Apply batch operations, returning per-op results."""
|
||||
results: List[Any] = []
|
||||
for op in operations:
|
||||
try:
|
||||
op_type = getattr(op, "_type", None)
|
||||
uri = getattr(op, "_uri", None)
|
||||
values = getattr(op, "_values", None)
|
||||
if op_type == 1 and uri is not None: # INSERT
|
||||
results.append(ContentProviderResult(uri=self.insert(uri, values or ContentValues())))
|
||||
elif op_type == 2 and uri is not None: # UPDATE
|
||||
count = self.update(uri, values or ContentValues(),
|
||||
getattr(op, "_selection", None),
|
||||
getattr(op, "_selection_args", None))
|
||||
results.append(ContentProviderResult(count=count))
|
||||
elif op_type == 3 and uri is not None: # DELETE
|
||||
count = self.delete(uri, getattr(op, "_selection", None),
|
||||
getattr(op, "_selection_args", None))
|
||||
results.append(ContentProviderResult(count=count))
|
||||
else:
|
||||
results.append(ContentProviderResult(count=0))
|
||||
except Exception:
|
||||
results.append(ContentProviderResult(count=0))
|
||||
return results
|
||||
|
||||
def bulkInsert(self, uri: Uri, values: List[ContentValues]) -> int:
|
||||
sys.stderr.write(f"[ContentResolver] bulkInsert: uri={uri}\n")
|
||||
return len(values) if values else 0
|
||||
count = 0
|
||||
for v in values or []:
|
||||
if self.insert(uri, v) is not None:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def call(self, uri: Uri, method: str, arg: Optional[str], extras: Optional[ContentValues]) -> Optional[ContentValues]:
|
||||
return None
|
||||
@@ -426,7 +527,17 @@ class ContentResolver(Stub):
|
||||
if not self._observers[uri_str]:
|
||||
del self._observers[uri_str]
|
||||
|
||||
def getType(self, uri: Uri) -> Optional[str]:
|
||||
def getType(self, uri) -> Optional[str]:
|
||||
"""返回 URI 指向资源的 MIME 类型。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path:
|
||||
mime, _ = mimetypes.guess_type(path)
|
||||
return mime or "application/octet-stream"
|
||||
scheme = getattr(uri, "_scheme", "") or ""
|
||||
if isinstance(uri, str) and "://" in uri:
|
||||
scheme = uri.split("://")[0]
|
||||
if scheme == "content":
|
||||
return "vnd.android.cursor.dir/vnd.android.content"
|
||||
return None
|
||||
|
||||
def acquireContentProviderClient(self, uri: Uri) -> Optional["ContentProviderClient"]:
|
||||
@@ -436,12 +547,40 @@ class ContentResolver(Stub):
|
||||
return ContentProviderClient(uri)
|
||||
|
||||
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
||||
return None
|
||||
"""返回 URI 支持的流 MIME 类型列表。"""
|
||||
mime = self.getType(uri)
|
||||
if mime is None:
|
||||
return None
|
||||
if mime_type_filter and mime_type_filter.endswith("/*"):
|
||||
prefix = mime_type_filter[:-1]
|
||||
if mime.startswith(prefix):
|
||||
return [mime]
|
||||
return None
|
||||
if mime == mime_type_filter:
|
||||
return [mime]
|
||||
return [mime] if mime else None
|
||||
|
||||
def openInputStream(self, uri: Uri) -> Any:
|
||||
"""打开输入流。对 file:// URI 返回真实文件流。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path and os.path.isfile(path):
|
||||
try:
|
||||
return open(path, "rb")
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
def openOutputStream(self, uri: Uri) -> Any:
|
||||
"""打开输出流。对 file:// URI 返回真实文件流。"""
|
||||
path = self._uri_to_file_path(uri)
|
||||
if path:
|
||||
try:
|
||||
d = os.path.dirname(path)
|
||||
if d:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
return open(path, "wb")
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
@@ -594,10 +733,16 @@ class Context(Stub):
|
||||
return PackageManager()
|
||||
|
||||
def getResources(self):
|
||||
return None
|
||||
from .content_res import Resources, AssetManager
|
||||
if not hasattr(self, "_resources") or self._resources is None:
|
||||
self._resources = Resources(self.getAssets())
|
||||
return self._resources
|
||||
|
||||
def getAssets(self):
|
||||
return None
|
||||
from .content_res import AssetManager
|
||||
if not hasattr(self, "_assets") or self._assets is None:
|
||||
self._assets = AssetManager()
|
||||
return self._assets
|
||||
|
||||
def getMainLooper(self):
|
||||
from .os import Looper
|
||||
@@ -668,15 +813,34 @@ class Context(Stub):
|
||||
pass
|
||||
|
||||
def getString(self, res_id: int, *args) -> str:
|
||||
"""返回资源字符串。优先委托给 Resources,否则返回合理默认值。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
val = res.getString(res_id, *args)
|
||||
if val is not None:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
return str(res_id)
|
||||
|
||||
def getColor(self, color_id: int) -> int:
|
||||
return 0
|
||||
"""返回资源颜色。优先委托给 Resources,否则返回不透明黑色。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
return res.getColor(color_id)
|
||||
except Exception:
|
||||
return 0xFF000000
|
||||
|
||||
def getDrawable(self, drawable_id: int):
|
||||
return None
|
||||
"""返回 Drawable。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
return res.getDrawable(drawable_id)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def getWallpaper(self):
|
||||
"""返回壁纸 Drawable。桌面端返回 None(无系统壁纸)。"""
|
||||
return None
|
||||
|
||||
def getWallpaperDesiredMinimumWidth(self) -> int:
|
||||
@@ -795,18 +959,55 @@ class Context(Stub):
|
||||
self._theme = resid
|
||||
|
||||
def getText(self, resId):
|
||||
"""返回资源文本。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
val = res.getText(resId)
|
||||
if val is not None:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
return str(resId)
|
||||
|
||||
def getQuantityString(self, resId, quantity, *formatArgs):
|
||||
return str(resId)
|
||||
"""返回复数字符串。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
return res.getQuantityString(resId, quantity, *formatArgs)
|
||||
except Exception:
|
||||
return str(resId)
|
||||
|
||||
def getStringArray(self, resId):
|
||||
"""返回字符串数组。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
val = res.getStringArray(resId)
|
||||
if val is not None:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
def getTextArray(self, resId):
|
||||
"""返回文本数组。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
val = res.getTextArray(resId)
|
||||
if val is not None:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
def getIntArray(self, resId):
|
||||
"""返回整型数组。优先委托给 Resources。"""
|
||||
try:
|
||||
res = self.getResources()
|
||||
val = res.getIntArray(resId)
|
||||
if val is not None:
|
||||
return val
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
# ─── Context 派生 / 回调 (扩展) ───────────────────────────
|
||||
@@ -1386,29 +1587,55 @@ class ContentProvider(Stub):
|
||||
|
||||
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
||||
selection_args: Optional[List[str]], sort_order: Optional[str]) -> Optional[Any]:
|
||||
"""默认实现:委托给关联 Context 的 ContentResolver。"""
|
||||
if self._context is not None:
|
||||
return self._context.getContentResolver().query(
|
||||
uri, projection, selection, selection_args, sort_order)
|
||||
return None
|
||||
|
||||
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
||||
if self._context is not None:
|
||||
return self._context.getContentResolver().insert(uri, values)
|
||||
return None
|
||||
|
||||
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
||||
selection_args: Optional[List[str]]) -> int:
|
||||
if self._context is not None:
|
||||
return self._context.getContentResolver().update(uri, values, selection, selection_args)
|
||||
return 0
|
||||
|
||||
def delete(self, uri: Uri, selection: Optional[str], selection_args: Optional[List[str]]) -> int:
|
||||
if self._context is not None:
|
||||
return self._context.getContentResolver().delete(uri, selection, selection_args)
|
||||
return 0
|
||||
|
||||
def getType(self, uri: Uri) -> Optional[str]:
|
||||
"""返回 URI 的 MIME 类型。"""
|
||||
path = ContentResolver._uri_to_file_path(uri)
|
||||
if path:
|
||||
mime, _ = mimetypes.guess_type(path)
|
||||
return mime or "application/octet-stream"
|
||||
return None
|
||||
|
||||
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
||||
return None
|
||||
mime = self.getType(uri)
|
||||
if mime is None:
|
||||
return None
|
||||
return [mime]
|
||||
|
||||
def openFile(self, uri: Uri, mode: str) -> Any:
|
||||
"""打开文件。对 file:// URI 返回真实文件对象。"""
|
||||
path = ContentResolver._uri_to_file_path(uri)
|
||||
if path and os.path.exists(path):
|
||||
try:
|
||||
fmode = "rb" if "r" in mode else "wb"
|
||||
return open(path, fmode)
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
def openAssetFile(self, uri: Uri, mode: str) -> Any:
|
||||
return None
|
||||
return self.openFile(uri, mode)
|
||||
|
||||
def attachInfo(self, context: Context, info: Any):
|
||||
self._context = context
|
||||
@@ -1444,29 +1671,44 @@ class ContentProviderClient(Stub):
|
||||
|
||||
def query(self, uri: Uri, projection: Optional[List[str]], selection: Optional[str],
|
||||
selection_args: Optional[List[str]], sort_order: Optional[str]) -> Optional[Any]:
|
||||
return None
|
||||
"""委托给 ContentResolver 处理 file:// URI。"""
|
||||
resolver = ContentResolver()
|
||||
return resolver.query(uri, projection, selection, selection_args, sort_order)
|
||||
|
||||
def insert(self, uri: Uri, values: ContentValues) -> Optional[Uri]:
|
||||
return uri
|
||||
resolver = ContentResolver()
|
||||
return resolver.insert(uri, values)
|
||||
|
||||
def update(self, uri: Uri, values: ContentValues, selection: Optional[str],
|
||||
selection_args: Optional[List[str]]) -> int:
|
||||
return 0
|
||||
resolver = ContentResolver()
|
||||
return resolver.update(uri, values, selection, selection_args)
|
||||
|
||||
def delete(self, uri: Uri, selection: Optional[str], selection_args: Optional[List[str]]) -> int:
|
||||
return 0
|
||||
resolver = ContentResolver()
|
||||
return resolver.delete(uri, selection, selection_args)
|
||||
|
||||
def applyBatch(self, operations: List[Any]) -> List[Any]:
|
||||
return []
|
||||
resolver = ContentResolver()
|
||||
return resolver.applyBatch("", operations)
|
||||
|
||||
def bulkInsert(self, uri: Uri, values: List[ContentValues]) -> int:
|
||||
return len(values) if values else 0
|
||||
resolver = ContentResolver()
|
||||
return resolver.bulkInsert(uri, values)
|
||||
|
||||
def getType(self, uri: Uri) -> Optional[str]:
|
||||
"""返回 URI 的 MIME 类型。"""
|
||||
path = ContentResolver._uri_to_file_path(uri)
|
||||
if path:
|
||||
mime, _ = mimetypes.guess_type(path)
|
||||
return mime or "application/octet-stream"
|
||||
return None
|
||||
|
||||
def getStreamTypes(self, uri: Uri, mime_type_filter: str) -> Optional[List[str]]:
|
||||
return None
|
||||
mime = self.getType(uri)
|
||||
if mime is None:
|
||||
return None
|
||||
return [mime]
|
||||
|
||||
def call(self, method: str, arg: Optional[str], extras: Optional[ContentValues]) -> Optional[ContentValues]:
|
||||
return None
|
||||
@@ -1478,13 +1720,21 @@ class ContentProviderClient(Stub):
|
||||
pass
|
||||
|
||||
def openFile(self, uri: Uri, mode: str) -> Any:
|
||||
"""打开文件。对 file:// URI 返回真实文件对象。"""
|
||||
path = ContentResolver._uri_to_file_path(uri)
|
||||
if path and os.path.exists(path):
|
||||
try:
|
||||
fmode = "rb" if "r" in mode else "wb"
|
||||
return open(path, fmode)
|
||||
except OSError:
|
||||
return None
|
||||
return None
|
||||
|
||||
def openAssetFile(self, uri: Uri, mode: str) -> Any:
|
||||
return None
|
||||
return self.openFile(uri, mode)
|
||||
|
||||
def openTypedAssetFile(self, uri: Uri, mime_type: str, opts: Optional[ContentValues]) -> Any:
|
||||
return None
|
||||
return self.openFile(uri, "r")
|
||||
|
||||
def getLocalContentProvider(self) -> Optional[ContentProvider]:
|
||||
return None
|
||||
@@ -1658,6 +1908,14 @@ class CursorLoader(Loader):
|
||||
self._sort_order = sort_order
|
||||
|
||||
def loadInBackground(self):
|
||||
"""在后台加载数据,委托给 ContentResolver.query。"""
|
||||
if self._context is not None:
|
||||
try:
|
||||
resolver = self._context.getContentResolver()
|
||||
return resolver.query(self._uri, self._projection, self._selection,
|
||||
self._selection_args, self._sort_order)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user