deveco_hmigbot 0.21.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +50 -0
  3. package/agents/hmigbot-worker.md +61 -0
  4. package/agents/hmigbot.md +22 -0
  5. package/agents/workflow-subagent.md +55 -0
  6. package/commands/hmigbot.md +17 -0
  7. package/dist/index.js +1 -0
  8. package/manifest.json +19 -0
  9. package/package.json +29 -0
  10. package/skills/migrate-core/FILES.md +26 -0
  11. package/skills/migrate-core/SKILL.md +484 -0
  12. package/skills/migrate-core/references/README.md +64 -0
  13. package/skills/migrate-core/references/flow/arkts-vector-gate.md +55 -0
  14. package/skills/migrate-core/references/flow/build-error-patterns.md +52 -0
  15. package/skills/migrate-core/references/flow/conventions-template.md +244 -0
  16. package/skills/migrate-core/references/flow/navigation-migration.md +42 -0
  17. package/skills/migrate-core/references/flow/platform-api-guards.md +59 -0
  18. package/skills/migrate-core/references/flow/platform-model-gaps.md +43 -0
  19. package/skills/migrate-core/references/flow/resource-conversion.md +46 -0
  20. package/skills/migrate-core/references/flow/ui-layout-semantics.md +124 -0
  21. package/skills/migrate-core/references/flow/unit-breakdown.md +42 -0
  22. package/skills/migrate-core/references/host-capabilities.md +24 -0
  23. package/skills/migrate-core/references/topics/app-identity.md +214 -0
  24. package/skills/migrate-core/references/topics/env-doctor.md +245 -0
  25. package/skills/migrate-core/references/topics/i18n/README.md +458 -0
  26. package/skills/migrate-core/references/topics/i18n/references/code-examples.md +304 -0
  27. package/skills/migrate-core/references/topics/i18n/references/common-pitfalls.md +354 -0
  28. package/skills/migrate-core/references/topics/i18n/references/dynamic-language-switch.md +464 -0
  29. package/skills/migrate-core/references/topics/i18n/references/language-codes.md +104 -0
  30. package/skills/migrate-core/references/topics/icon-sizing.md +98 -0
  31. package/skills/migrate-core/references/topics/library-migration/README.md +234 -0
  32. package/skills/migrate-core/references/topics/library-migration/closed-source-sdk.md +128 -0
  33. package/skills/migrate-core/references/topics/library-migration/download-api-decision.md +84 -0
  34. package/skills/migrate-core/references/topics/library-migration/library-mapping-table.md +100 -0
  35. package/skills/migrate-core/references/topics/library-migration/napi-compile-guide.md +84 -0
  36. package/skills/migrate-core/references/topics/library-migration/ohpm-search-guide.md +73 -0
  37. package/skills/migrate-core/references/topics/library-migration/stdlib-mapping-table.md +34 -0
  38. package/skills/migrate-core/references/topics/resources/aar-decompile.md +25 -0
  39. package/skills/migrate-core/references/topics/resources/conversion-rules.md +625 -0
  40. package/skills/migrate-core/references/topics/resources/dependency-analysis-rules.md +328 -0
  41. package/skills/migrate-core/references/topics/resources/material-design-icons.md +173 -0
  42. package/skills/migrate-core/references/topics/resources/svg-fix-patterns.md +175 -0
  43. package/skills/migrate-core/references/topics/resources/xml-drawable-to-svg-rules.md +513 -0
  44. package/skills/migrate-core/references/topics/system-capabilities/README.md +331 -0
  45. package/skills/migrate-core/references/topics/system-capabilities/avplayer-guide.md +161 -0
  46. package/skills/migrate-core/references/topics/system-capabilities/background-tasks.md +403 -0
  47. package/skills/migrate-core/references/topics/system-capabilities/browser-intent.md +121 -0
  48. package/skills/migrate-core/references/topics/system-capabilities/camera-picker.md +118 -0
  49. package/skills/migrate-core/references/topics/system-capabilities/document-picker.md +246 -0
  50. package/skills/migrate-core/references/topics/system-capabilities/file-utils.md +131 -0
  51. package/skills/migrate-core/references/topics/system-capabilities/permission-helper.md +112 -0
  52. package/skills/migrate-core/references/topics/system-capabilities/photo-access-helper.md +208 -0
  53. package/skills/migrate-core/references/topics/system-capabilities/print-management.md +213 -0
  54. package/skills/migrate-core/references/topics/system-capabilities/share-panel.md +177 -0
  55. package/skills/migrate-core/references/topics/system-capabilities/system-settings.md +322 -0
  56. package/skills/migrate-core/references/topics/system-capabilities/telephony-dial.md +49 -0
  57. package/skills/migrate-core/references/topics/system-capabilities/video-playback.md +42 -0
  58. package/skills/migrate-core/references/topics/system-capabilities/webview-patterns.md +38 -0
  59. package/skills/migrate-core/references/topics/ui-alignment/README.md +344 -0
  60. package/skills/migrate-core/references/topics/ui-alignment/references/dark-mode.md +47 -0
  61. package/skills/migrate-core/references/topics/ui-alignment/references/layout-mapping.md +301 -0
  62. package/skills/migrate-core/references/topics/ui-alignment/references/visual-patterns.md +411 -0
  63. package/skills/migrate-core/scripts/closure/check-anchors.mjs +186 -0
  64. package/skills/migrate-core/scripts/closure/check-api-guards.mjs +175 -0
  65. package/skills/migrate-core/scripts/closure/check-consumers.mjs +301 -0
  66. package/skills/migrate-core/scripts/closure/check-permissions.mjs +165 -0
  67. package/skills/migrate-core/scripts/closure/check-resources.mjs +130 -0
  68. package/skills/migrate-core/scripts/closure/check-routes.mjs +527 -0
  69. package/skills/migrate-core/scripts/closure/check-safearea.mjs +122 -0
  70. package/skills/migrate-core/scripts/closure/check-stubs.mjs +69 -0
  71. package/skills/migrate-core/scripts/closure/closure-suite.mjs +256 -0
  72. package/skills/migrate-core/scripts/closure/idioms.json +105 -0
  73. package/skills/migrate-core/scripts/convert/convert-resources.mjs +437 -0
  74. package/skills/migrate-core/scripts/feasibility/feasibility.mjs +235 -0
  75. package/skills/migrate-core/scripts/feasibility/tables/cross-platform.json +11 -0
  76. package/skills/migrate-core/scripts/feasibility/tables/deprecated-api.json +10 -0
  77. package/skills/migrate-core/scripts/feasibility/tables/imported-arkts-core.json +425 -0
  78. package/skills/migrate-core/scripts/feasibility/tables/lib-equivalence.json +206 -0
  79. package/skills/migrate-core/scripts/feasibility/tables/system-capabilities.json +22 -0
  80. package/skills/migrate-core/scripts/front.mjs +107 -0
  81. package/skills/migrate-core/scripts/interface/ark-extract.mjs +172 -0
  82. package/skills/migrate-core/scripts/interface/interface.mjs +152 -0
  83. package/skills/migrate-core/scripts/ledger/ledger.mjs +383 -0
  84. package/skills/migrate-core/scripts/ledger/parse-cards.mjs +98 -0
  85. package/skills/migrate-core/scripts/lib/literals.mjs +37 -0
  86. package/skills/migrate-core/scripts/lib/scan.mjs +315 -0
  87. package/skills/migrate-core/scripts/smoke/align-sdk.mjs +118 -0
  88. package/skills/migrate-core/scripts/smoke/ensure-sign.mjs +53 -0
  89. package/skills/migrate-core/scripts/smoke/smoke.mjs +238 -0
  90. package/skills/migrate-core/scripts/smoke/verdict.mjs +31 -0
  91. package/skills/migrate-core/scripts/smoke/walk.mjs +480 -0
  92. package/skills/migrate-core/scripts/transpile/mapping.json +76 -0
  93. package/skills/migrate-core/scripts/transpile/transpile-layout.mjs +404 -0
  94. package/skills/migrate-core/scripts/vectors/run-arkts-vectors.mjs +107 -0
  95. package/skills/migrate-core/scripts/vectors/setup-arkts-test.mjs +90 -0
  96. package/skills/migrate-core/scripts/wire/extractors.mjs +258 -0
  97. package/skills/migrate-core/scripts/wire/wire-routes.mjs +507 -0
  98. package/skills/migrate-core/templates/acceptance.js +365 -0
  99. package/skills/migrate-core/templates/explore.js +86 -0
  100. package/skills/migrate-core/templates/implement.js +211 -0
  101. package/skills/migrate-core/templates/mig_slices.js +491 -0
@@ -0,0 +1,246 @@
1
+ # 文件管理器 DocumentViewPicker
2
+
3
+ > 使用 `@kit.CoreFileKit` 的 `picker` 模块拉起系统文件管理器,替代 Android 的 `Intent.ACTION_OPEN_DOCUMENT` 和 `Intent.ACTION_CREATE_DOCUMENT`。
4
+
5
+ ---
6
+
7
+ ## 基本导入
8
+
9
+ ```typescript
10
+ import { picker } from '@kit.CoreFileKit';
11
+ import { common } from '@kit.AbilityKit';
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 唤起文件管理器(选择文件)
17
+
18
+ > 对应 Android:`Intent.ACTION_OPEN_DOCUMENT`
19
+
20
+ ### 基础用法
21
+
22
+ ```typescript
23
+ function openFileManager(context: common.UIAbilityContext): void {
24
+ const docPicker = new picker.DocumentViewPicker(context);
25
+ const selectOptions = new picker.DocumentSelectOptions();
26
+
27
+ docPicker.select(selectOptions)
28
+ .then((uris: Array<string>) => {
29
+ hilog.info(DOMAIN, TAG, `Selected files: ${uris.length}`);
30
+ // uris 是用户选择的文件 URI 数组
31
+ uris.forEach(uri => {
32
+ hilog.info(DOMAIN, TAG, `Selected URI: ${uri}`);
33
+ });
34
+ })
35
+ .catch((err: Error) => {
36
+ hilog.error(DOMAIN, TAG, `Failed to open file manager: ${err.message}`);
37
+ });
38
+ }
39
+ ```
40
+
41
+ ### 限定文件后缀
42
+
43
+ ```typescript
44
+ function openFileManagerWithFilter(context: common.UIAbilityContext): void {
45
+ const docPicker = new picker.DocumentViewPicker(context);
46
+ const selectOptions = new picker.DocumentSelectOptions();
47
+
48
+ // 仅允许选择特定后缀的文件
49
+ selectOptions.fileSuffixFilters = ['.txt', '.pdf', '.epub', '.mht'];
50
+
51
+ docPicker.select(selectOptions)
52
+ .then((uris: Array<string>) => {
53
+ if (uris.length > 0) {
54
+ hilog.info(DOMAIN, TAG, `Selected: ${uris[0]}`);
55
+ }
56
+ })
57
+ .catch((err: Error) => {
58
+ hilog.error(DOMAIN, TAG, `Select error: ${err.message}`);
59
+ });
60
+ }
61
+ ```
62
+
63
+ ---
64
+
65
+ ## 唤起文件管理器(保存文件)
66
+
67
+ > 对应 Android:`Intent.ACTION_CREATE_DOCUMENT`
68
+
69
+ ### 基础用法
70
+
71
+ ```typescript
72
+ function saveFileToDownloads(context: common.UIAbilityContext, fileName: string, content: string): void {
73
+ const docPicker = new picker.DocumentViewPicker(context);
74
+ const saveOptions = new picker.DocumentSaveOptions();
75
+
76
+ // 设置默认文件名
77
+ saveOptions.newFileNames = [fileName];
78
+ // 限定可保存的文件后缀
79
+ saveOptions.fileSuffixChoices = ['.txt', '.mht', '.html'];
80
+
81
+ docPicker.save(saveOptions)
82
+ .then((uris: Array<string>) => {
83
+ if (uris.length > 0) {
84
+ hilog.info(DOMAIN, TAG, `File saved to: ${uris[0]}`);
85
+ // 通过 URI 写入文件内容(需配合 fileIo)
86
+ }
87
+ })
88
+ .catch((err: Error) => {
89
+ hilog.error(DOMAIN, TAG, `Save cancelled or failed: ${err.message}`);
90
+ });
91
+ }
92
+ ```
93
+
94
+ ### 完整示例:保存网页为 MHT
95
+
96
+ ```typescript
97
+ import { picker } from '@kit.CoreFileKit';
98
+ import { fileIo } from '@kit.CoreFileKit';
99
+ import { common } from '@kit.AbilityKit';
100
+
101
+ function saveWebPage(context: common.UIAbilityContext, title: string, content: string): void {
102
+ const docPicker = new picker.DocumentViewPicker(context);
103
+ const saveOptions = new picker.DocumentSaveOptions();
104
+
105
+ // 清理文件名中的非法字符
106
+ let fileName = title || 'page';
107
+ if (fileName.length > 50) {
108
+ fileName = fileName.substring(0, 50);
109
+ }
110
+ fileName = fileName.replace(/[\\/:*?"<>|]/g, '_');
111
+
112
+ saveOptions.newFileNames = [fileName + '.mht'];
113
+ saveOptions.fileSuffixChoices = ['MHT|.mht'];
114
+
115
+ docPicker.save(saveOptions)
116
+ .then((uris: Array<string>) => {
117
+ if (uris.length > 0) {
118
+ hilog.info(DOMAIN, TAG, `Saved to: ${uris[0]}`);
119
+ // 写入文件内容
120
+ try {
121
+ const file = fileIo.openSync(uris[0], fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE);
122
+ fileIo.writeSync(file.fd, content);
123
+ fileIo.closeSync(file.fd);
124
+ hilog.info(DOMAIN, TAG, 'File content written successfully');
125
+ } catch (writeErr) {
126
+ hilog.error(DOMAIN, TAG, `Write file error: ${writeErr.message}`);
127
+ }
128
+ }
129
+ })
130
+ .catch((err: Error) => {
131
+ hilog.info(DOMAIN, TAG, `Save cancelled: ${err.message}`);
132
+ });
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## DocumentSelectOptions 配置项
139
+
140
+ | 属性 | 类型 | 说明 |
141
+ |------|------|------|
142
+ | `fileSuffixFilters` | `string[]` | 限定可选择的文件后缀,如 `['.txt', '.pdf']` |
143
+ | `subFuzzingNumber` | `number` | 子目录搜索深度,默认 0(仅当前目录) |
144
+
145
+ ---
146
+
147
+ ## DocumentSaveOptions 配置项
148
+
149
+ | 属性 | 类型 | 说明 |
150
+ |------|------|------|
151
+ | `newFileNames` | `string[]` | 默认文件名数组(第一个为默认名) |
152
+ | `fileSuffixChoices` | `string[]` | 允许保存的文件后缀列表,格式如 `'MHT|.mht'` 或 `'.mht'` |
153
+
154
+ ---
155
+
156
+ ## DocumentViewPicker 其他方法
157
+
158
+ | 方法 | 用途 | 对应 Android |
159
+ |------|------|-------------|
160
+ | `.select(options)` | 选择文件(可多选) | `ACTION_OPEN_DOCUMENT` |
161
+ | `.save(options)` | 保存文件 | `ACTION_CREATE_DOCUMENT` |
162
+
163
+ ---
164
+
165
+ ## module.json5 配置
166
+
167
+ > ⚠️ **重要**:`DocumentViewPicker` 是系统内置 picker,通常**不需要**额外配置 module.json5。
168
+
169
+ 以下情况可能需要配置:
170
+ - 应用需要作为文件提供者响应其他应用的文件请求
171
+ - 需要声明特定的 file 类型支持
172
+
173
+ ```json5
174
+ {
175
+ "skills": [{
176
+ "entities": ["entity.system.home"],
177
+ "actions": [
178
+ "ohos.want.action.home"
179
+ ]
180
+ }]
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## 常见错误
187
+
188
+ ### 错误 1:选择文件后未正确处理 URI
189
+
190
+ ```typescript
191
+ // 错误 — 直接使用 URI 访问文件
192
+ const data = fileIo.readFileSync(uri); // ❌ 不支持直接路径
193
+
194
+ // 正确 — 使用 fileIo.openSync 获取 fd
195
+ const file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY);
196
+ const data = fileIo.readSync(file.fd);
197
+ fileIo.closeSync(file.fd);
198
+ ```
199
+
200
+ ### 错误 2:保存文件时文件名包含非法字符
201
+
202
+ ```typescript
203
+ // 错误 — Windows 文件名不允许以下字符
204
+ saveOptions.newFileNames = ['file:name?.mht'];
205
+
206
+ // 正确 — 替换非法字符
207
+ let safeName = fileName.replace(/[\\/:*?"<>|]/g, '_');
208
+ saveOptions.newFileNames = [safeName + '.mht'];
209
+ ```
210
+
211
+ ### 错误 3:save() 的 Promise 被静默取消
212
+
213
+ ```typescript
214
+ // 错误 — 用户取消时可能静默失败
215
+ docPicker.save(saveOptions)
216
+ .then((uris) => {
217
+ if (uris.length > 0) { /* 保存成功 */ }
218
+ // ⚠️ 用户取消时 uris 为空数组,但没有提示
219
+ });
220
+
221
+ // 正确 — 用 finally 或区分处理
222
+ docPicker.save(saveOptions)
223
+ .then((uris) => {
224
+ if (uris.length > 0) {
225
+ hilog.info(DOMAIN, TAG, 'Save success');
226
+ }
227
+ })
228
+ .catch((err) => {
229
+ hilog.error(DOMAIN, TAG, 'Save failed');
230
+ })
231
+ .finally(() => {
232
+ // 用户取消也会走到这里
233
+ hilog.info(DOMAIN, TAG, 'Save dialog closed');
234
+ });
235
+ ```
236
+
237
+ ---
238
+
239
+ ## 与 cameraPicker 的对比
240
+
241
+ | 维度 | DocumentViewPicker(本节) | cameraPicker |
242
+ |------|--------------------------|--------------|
243
+ | 所属 Kit | `@kit.CoreFileKit` | `@kit.CameraKit` |
244
+ | 功能 | 选择/保存文件 | 拍照/录像 |
245
+ | 返回 | `Promise<Array<string>>`(URI 数组) | `Promise<PickerResult>` |
246
+ | module.json5 配置 | 通常不需要 | 需要 `entity.system.camera` |
@@ -0,0 +1,131 @@
1
+ # 文件操作指南
2
+
3
+ > @kit.CoreFileKit 文件读写的完整使用模式,替代 Android 的 java.io.File。
4
+
5
+ ---
6
+
7
+ ## 基本导入
8
+
9
+ ```typescript
10
+ import { fileIo } from '@kit.CoreFileKit'
11
+ import { util } from '@kit.ArkTS'
12
+ ```
13
+
14
+ ---
15
+
16
+ ## 沙箱路径
17
+
18
+ HarmonyOS 应用只能访问自己的沙箱目录:
19
+
20
+ ```typescript
21
+ // 获取沙箱路径
22
+ const filesDir = context.filesDir // 持久化存储目录
23
+ const cacheDir = context.cacheDir // 缓存目录(系统可能自动清理)
24
+ const tempDir = context.tempDir // 临时目录
25
+ ```
26
+
27
+ **关键规则**:
28
+ - ✅ 只能使用 `context.filesDir`、`context.cacheDir`、`context.tempDir`
29
+ - ❌ 不能写入 `/storage/...` 等绝对路径(Permission denied)
30
+
31
+ ---
32
+
33
+ ## 文件读写
34
+
35
+ ### 写入文件
36
+
37
+ ```typescript
38
+ function writeFile(context: Context, fileName: string, content: string): void {
39
+ const filePath = `${context.filesDir}/${fileName}`
40
+ const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
41
+ fileIo.writeSync(file.fd, content)
42
+ fileIo.closeSync(file.fd)
43
+ }
44
+ ```
45
+
46
+ ### 读取文件
47
+
48
+ ```typescript
49
+ function readFile(context: Context, fileName: string): string {
50
+ const filePath = `${context.filesDir}/${fileName}`
51
+ const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY)
52
+ const stat = fileIo.statSync(filePath)
53
+ const buffer = new ArrayBuffer(stat.size)
54
+ fileIo.readSync(file.fd, buffer)
55
+ fileIo.closeSync(file.fd)
56
+
57
+ const decoder = new util.TextDecoder('utf-8')
58
+ return decoder.decodeWithStream(new Uint8Array(buffer))
59
+ }
60
+ ```
61
+
62
+ ### 检查文件是否存在
63
+
64
+ ```typescript
65
+ function fileExists(path: string): boolean {
66
+ try {
67
+ fileIo.accessSync(path)
68
+ return true
69
+ } catch {
70
+ return false
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### 创建目录
76
+
77
+ ```typescript
78
+ function ensureDir(dirPath: string): void {
79
+ try {
80
+ fileIo.mkdirSync(dirPath, true) // true = 递归创建
81
+ } catch {
82
+ // 目录已存在
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### 删除文件
88
+
89
+ ```typescript
90
+ function deleteFile(path: string): void {
91
+ try {
92
+ fileIo.unlinkSync(path)
93
+ } catch {
94
+ // 文件不存在
95
+ }
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 下载文件到沙箱
102
+
103
+ ```typescript
104
+ import { http } from '@kit.NetworkKit'
105
+
106
+ async function downloadToSandbox(
107
+ context: Context,
108
+ url: string,
109
+ fileName: string
110
+ ): Promise<string> {
111
+ const savePath = `${context.filesDir}/downloads`
112
+ ensureDir(savePath)
113
+
114
+ const filePath = `${savePath}/${fileName}`
115
+ const httpRequest = http.createHttp()
116
+ try {
117
+ const response = await httpRequest.request(url, {
118
+ method: http.RequestMethod.GET,
119
+ expectDataType: http.HttpDataType.ARRAY_BUFFER
120
+ })
121
+ if (response.responseCode === 200) {
122
+ const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY)
123
+ fileIo.writeSync(file.fd, response.result as ArrayBuffer)
124
+ fileIo.closeSync(file.fd)
125
+ }
126
+ } finally {
127
+ httpRequest.destroy()
128
+ }
129
+ return filePath
130
+ }
131
+ ```
@@ -0,0 +1,112 @@
1
+ # 权限请求与检查
2
+
3
+ > ⚠ 本文样例中 `getContext(this)` 为已废弃写法:组件内一律改 `this.getUIContext().getHostContext()`;Ability 内用 `this.context`。
4
+
5
+
6
+ > abilityAccessCtrl 权限管理的完整使用模式,替代 Android 的 ActivityCompat.requestPermissions。
7
+
8
+ ---
9
+
10
+ ## 基本导入
11
+
12
+ ```typescript
13
+ import { abilityAccessCtrl, Permissions, bundleManager, common } from '@kit.AbilityKit'
14
+ ```
15
+
16
+ ---
17
+
18
+ ## 完整权限请求封装
19
+
20
+ ```typescript
21
+ // helpers/PermissionHelper.ets
22
+ export class PermissionHelper {
23
+ /**
24
+ * 检查并请求权限
25
+ * @returns true 表示已获得所有权限
26
+ */
27
+ static async requestPermissions(
28
+ context: common.UIAbilityContext,
29
+ permissions: Permissions[]
30
+ ): Promise<boolean> {
31
+ const atManager = abilityAccessCtrl.createAtManager()
32
+
33
+ // 先检查是否已有权限
34
+ const allGranted = await PermissionHelper.checkPermissions(context, permissions)
35
+ if (allGranted) return true
36
+
37
+ // 请求权限
38
+ const result = await atManager.requestPermissionsFromUser(context, permissions)
39
+
40
+ // 检查结果
41
+ for (let i = 0; i < result.authResults.length; i++) {
42
+ if (result.authResults[i] !== 0) {
43
+ return false // 有权限被拒绝
44
+ }
45
+ }
46
+ return true
47
+ }
48
+
49
+ /**
50
+ * 检查权限状态
51
+ */
52
+ static async checkPermissions(
53
+ context: common.UIAbilityContext,
54
+ permissions: Permissions[]
55
+ ): Promise<boolean> {
56
+ const atManager = abilityAccessCtrl.createAtManager()
57
+ const bundleInfo = await bundleManager.getBundleInfoForSelf(
58
+ bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
59
+ )
60
+ const tokenId = bundleInfo.appInfo.accessTokenId
61
+
62
+ for (const permission of permissions) {
63
+ const grantStatus = await atManager.checkAccessToken(tokenId, permission)
64
+ if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
65
+ return false
66
+ }
67
+ }
68
+ return true
69
+ }
70
+ }
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 使用示例
76
+
77
+ ```typescript
78
+ // 在页面中请求权限
79
+ async aboutToAppear(): Promise<void> {
80
+ const context = getContext(this) as common.UIAbilityContext
81
+ const granted = await PermissionHelper.requestPermissions(context, [
82
+ 'ohos.permission.CAMERA' as Permissions,
83
+ 'ohos.permission.MICROPHONE' as Permissions,
84
+ ])
85
+
86
+ if (granted) {
87
+ await this.loadData()
88
+ } else {
89
+ // 提示用户去设置页面开启权限
90
+ this.showPermissionDeniedTip()
91
+ }
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 常用权限列表
98
+
99
+ | 权限 | 用途 | 类型 |
100
+ |------|------|------|
101
+ | `ohos.permission.INTERNET` | 网络访问 | system_grant(自动授权)|
102
+ | `ohos.permission.CAMERA` | 相机 | user_grant(需用户同意)|
103
+ | `ohos.permission.MICROPHONE` | 麦克风 | user_grant |
104
+ | `ohos.permission.APPROXIMATELY_LOCATION` | 模糊定位 | user_grant |
105
+ | `ohos.permission.LOCATION` | 精确定位 | user_grant,**须与 APPROXIMATELY_LOCATION 一起申请**,不能单独申请 |
106
+ | `ohos.permission.KEEP_BACKGROUND_RUNNING` | 后台运行 | system_grant |
107
+ | `ohos.permission.READ_IMAGEVIDEO` / `WRITE_IMAGEVIDEO` | 全量读/直写媒体库 | **受限开放**(system_basic,须 ACL)——三方应用改用 PhotoViewPicker / SaveButton(免权限) |
108
+ | `ohos.permission.READ_CONTACTS` / `WRITE_CONTACTS` | 读/写通讯录 | **受限开放**——改用 `contact.selectContacts`(系统选人,免权限) |
109
+
110
+ **system_grant**:在 module.json5 中声明即可,系统自动授权。
111
+ **user_grant**:需要在 module.json5 声明 + 代码运行时请求用户同意。
112
+ **受限开放权限**(READ/WRITE_IMAGEVIDEO、READ/WRITE_CONTACTS、READ/WRITE_AUDIO、SYSTEM_FLOAT_WINDOW、READ_PASTEBOARD 等):normal 应用须 ACL 才能申请,默认拿不到——改走系统 Picker / 安全控件。权限名以 SDK `ets/api/permissions.d.ts` 为准,是否受限以官方"受限开放权限"清单为准。
@@ -0,0 +1,208 @@
1
+ # photoAccessHelper 媒体查询指南
2
+
3
+ > HarmonyOS 媒体库访问的完整使用模式,替代 Android 的 MediaStore + ContentProvider。
4
+
5
+ ## 权限口径(先定路线再写码)
6
+
7
+ | 路线 | API | 权限 | 适用 |
8
+ |---|---|---|---|
9
+ | 选图 | `new photoAccessHelper.PhotoViewPicker().select(options)` | 无需权限;返回 uri 只读,不在 picker 回调里直接开文件,存下来由按钮等事件再读 | 三方应用默认 |
10
+ | 存图 | `SaveButton` 安全控件 `onClick` 内 `MediaAssetChangeRequest.createImageAssetRequest / createAssetRequest` + `applyChanges` | 无需权限(点击态临时授权) | 三方应用默认 |
11
+ | 存图 | `phAccessHelper.showAssetsCreationDialog(srcUris, configs)` 拿目标 uri 后 `fileIo` 写入 | 无需权限(弹窗授权) | 三方应用默认 |
12
+ | 全量读 | `getAssets` / `getAlbums` | `ohos.permission.READ_IMAGEVIDEO`——**受限开放权限**(system_basic,须 ACL) | 仅拿到 ACL 的应用 |
13
+ | 直写 | `createAsset`、`SaveButton` 之外调用的 `applyChanges` | `ohos.permission.WRITE_IMAGEVIDEO`——受限开放权限 | 仅拿到 ACL 的应用 |
14
+
15
+ Android `MediaStore` 查询默认映射到"选图"路线(用户从系统图库选),不是 `getAssets`;相册写入映射到 `SaveButton` 路线。
16
+
17
+ ---
18
+
19
+ ## 选图(PhotoViewPicker,无需权限)
20
+
21
+ ```typescript
22
+ import { photoAccessHelper } from '@kit.MediaLibraryKit'
23
+ import { BusinessError } from '@kit.BasicServicesKit'
24
+
25
+ @State pickedUris: string[] = []
26
+
27
+ async pickImages(): Promise<void> {
28
+ const options = new photoAccessHelper.PhotoSelectOptions()
29
+ options.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE
30
+ options.maxSelectNumber = 9
31
+ try {
32
+ const result: photoAccessHelper.PhotoSelectResult = await new photoAccessHelper.PhotoViewPicker().select(options)
33
+ this.pickedUris = result.photoUris // 只读 uri;读文件放到按钮等事件里做,不在此回调里直接 open
34
+ } catch (e) {
35
+ const err = e as BusinessError // 用户取消也走这里:保持原状或给可见提示
36
+ }
37
+ }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 基本导入与初始化
43
+
44
+ ```typescript
45
+ import { photoAccessHelper } from '@kit.MediaLibraryKit'
46
+ import { dataSharePredicates } from '@kit.ArkData'
47
+
48
+ const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 全量查询媒体资源(getAssets:需 `READ_IMAGEVIDEO`,受限开放——只在拿到 ACL 时用)
54
+
55
+ ```typescript
56
+ async function getMediaAssets(context: Context): Promise<photoAccessHelper.PhotoAsset[]> {
57
+ const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)
58
+
59
+ const fetchOptions: photoAccessHelper.FetchOptions = {
60
+ fetchColumns: [
61
+ photoAccessHelper.PhotoKeys.URI,
62
+ photoAccessHelper.PhotoKeys.DISPLAY_NAME,
63
+ photoAccessHelper.PhotoKeys.SIZE,
64
+ photoAccessHelper.PhotoKeys.DATE_ADDED,
65
+ photoAccessHelper.PhotoKeys.DURATION,
66
+ photoAccessHelper.PhotoKeys.WIDTH,
67
+ photoAccessHelper.PhotoKeys.HEIGHT,
68
+ photoAccessHelper.PhotoKeys.PHOTO_TYPE,
69
+ ],
70
+ predicates: new dataSharePredicates.DataSharePredicates()
71
+ }
72
+
73
+ const fetchResult = await phAccessHelper.getAssets(fetchOptions)
74
+ const count = fetchResult.getCount()
75
+ const assets: photoAccessHelper.PhotoAsset[] = []
76
+
77
+ if (count > 0) {
78
+ let asset = await fetchResult.getFirstObject()
79
+ let index = 0
80
+ while (index < count) {
81
+ assets.push(asset)
82
+ index++
83
+ if (index < count) {
84
+ asset = await fetchResult.getNextObject()
85
+ }
86
+ }
87
+ }
88
+
89
+ fetchResult.close() // 必须关闭!
90
+ return assets
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## FetchResult 遍历模式(关键)
97
+
98
+ **FetchResult 不是数组**,是游标式迭代器。必须按以下模式遍历:
99
+
100
+ ```typescript
101
+ // ✅ 正确模式
102
+ let asset = await fetchResult.getFirstObject()
103
+ let index = 0
104
+ while (index < count) {
105
+ // 处理 asset
106
+ processAsset(asset)
107
+ index++
108
+ if (index < count) {
109
+ asset = await fetchResult.getNextObject()
110
+ }
111
+ }
112
+ fetchResult.close() // 必须关闭
113
+
114
+ // ❌ 错误 — 不能用 for...of
115
+ for (const asset of fetchResult) { ... } // 不支持
116
+
117
+ // ❌ 错误 — 不能一次性获取全部
118
+ const allAssets = await fetchResult.getAllObjects() // 大量数据会内存溢出
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 获取文件 URI
124
+
125
+ ```typescript
126
+ // 获取资源 URI(可用于 Image 组件显示)
127
+ const uri = asset.uri // 格式: file://media/Photo/xxx
128
+
129
+ // ⚠️ 视频 URI 不能传给 Image 组件(会显示灰色空白)
130
+ // 图片 URI 可以直接传给 Image 组件
131
+ Image(asset.uri)
132
+ .sourceSize({ width: 256, height: 256 }) // 缩略图优化
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 按类型筛选
138
+
139
+ ```typescript
140
+ // 只查图片
141
+ const predicates = new dataSharePredicates.DataSharePredicates()
142
+ predicates.equalTo(photoAccessHelper.PhotoKeys.PHOTO_TYPE, photoAccessHelper.PhotoType.IMAGE)
143
+
144
+ // 只查视频
145
+ predicates.equalTo(photoAccessHelper.PhotoKeys.PHOTO_TYPE, photoAccessHelper.PhotoType.VIDEO)
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 保存图片到系统相册(MediaAssetChangeRequest 三步)
151
+
152
+ 把 app 生成/下载的图片写入系统相册,**走资产变更请求范式**,不要臆造一步式 `saveImage`,也**不要用 `phAccessHelper.createAsset(...)` + `fileIo.open(uri)` + `copyFile` 直写文件**(`createAsset` 需受限权限 `WRITE_IMAGEVIDEO`)。
153
+ `applyChanges` 免权限的前提是**在 `SaveButton` 安全控件的 `onClick` 里调用**(点击态临时授权);在别处调用同样需要 `WRITE_IMAGEVIDEO`。
154
+
155
+ 三步(编译校验过):
156
+
157
+ ```typescript
158
+ import { photoAccessHelper } from '@kit.MediaLibraryKit'
159
+ import { common } from '@kit.AbilityKit'
160
+
161
+ // srcFileUri:沙箱内源图片的 file:// URI(沙箱路径可用 fileUri.getUriFromPath 转换)
162
+ async function saveImageToAlbum(context: common.UIAbilityContext, srcFileUri: string): Promise<void> {
163
+ const phAccessHelper: photoAccessHelper.PhotoAccessHelper =
164
+ photoAccessHelper.getPhotoAccessHelper(context)
165
+
166
+ // 1) 创建图片资产变更请求(photoType + 扩展名;options 可选)
167
+ const req: photoAccessHelper.MediaAssetChangeRequest =
168
+ photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(
169
+ context, photoAccessHelper.PhotoType.IMAGE, 'jpg'
170
+ )
171
+
172
+ // 2) 注入图片数据:源文件 URI(string)或 ArrayBuffer 二选一
173
+ req.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, srcFileUri)
174
+
175
+ // 3) 提交生效(异步,必须 await / then)
176
+ await phAccessHelper.applyChanges(req)
177
+ }
178
+ ```
179
+
180
+ 触发点必须是 `SaveButton`(安全控件)的 `onClick`——点击态临时授权,免 `WRITE_IMAGEVIDEO`:
181
+
182
+ ```typescript
183
+ SaveButton({ icon: SaveIconStyle.FULL_FILLED, text: SaveDescription.SAVE_IMAGE, buttonType: ButtonType.Capsule })
184
+ .onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {
185
+ if (result === SaveButtonOnClickResult.SUCCESS) {
186
+ await saveImageToAlbum(this.getUIContext().getHostContext() as common.UIAbilityContext, srcFileUri)
187
+ }
188
+ })
189
+ ```
190
+
191
+ 关键签名与要点:
192
+
193
+ | 项 | 内容 |
194
+ |----|------|
195
+ | 创建请求 | `MediaAssetChangeRequest.createAssetRequest(context: Context, photoType: PhotoType, extension: string, options?: CreateOptions): MediaAssetChangeRequest` |
196
+ | 注入数据 | `req.addResource(type: ResourceType, fileUri: string \| ArrayBuffer): void`,类型用 `photoAccessHelper.ResourceType.IMAGE_RESOURCE` |
197
+ | 提交 | `await phAccessHelper.applyChanges(req)`(返回 Promise) |
198
+ | 权限 | `SaveButton.onClick` 内调用免权限;其它位置需 `ohos.permission.WRITE_IMAGEVIDEO`(受限开放,须 ACL) |
199
+
200
+ > `addResource` 传源文件 URI 时,须确保该 URI 对应的资源实际存在。已有现成图片文件时也可用 `MediaAssetChangeRequest.createImageAssetRequest(context, fileUri)` 一步建请求再 `applyChanges`。
201
+
202
+ ---
203
+
204
+ ## 权限要求
205
+
206
+ 三方应用默认路线(PhotoViewPicker / SaveButton / showAssetsCreationDialog)**不声明任何媒体权限**。
207
+ 只有拿到 ACL 的应用才声明 `ohos.permission.READ_IMAGEVIDEO` / `WRITE_IMAGEVIDEO`(受限开放权限,system_basic;普通应用声明后签名即失败)。
208
+ 写相册不能靠只读权限:`READ_IMAGEVIDEO` 不覆盖写入。