README.en.md
1# PrintSpooler<a name="EN-US_TOPIC_0000001103330836"></a>
2
3- [Introduction](#section11660541593)
4 - [Architecture](#section125101832114213)
5
6- [Directory Structure](#section161941989596)
7- [Repositories Involved](#section1371113476307)
8
9## Introduction<a name="section11660541593"></a>
10
11PrintSpooler is a system app preinstalled in OpenHarmony. It provides users with functions such as print preview, discovering and connecting printers, setting print parameters, issuing print tasks, and managing print tasks.
12
13### Architecture<a name="section125101832114213"></a>
14
15![](figures/spooler_01.png)
16
17## Directory Structure<a name="section161941989596"></a>
18
19```
20/applications/standard/print_spooler
21├── figures # Architecture figures
22├── entry # Main entry module code
23├── signature # Certificate files
24├── LICENSE # License files
25├── features
26 ├── ippPrint # ipp print
27```
28
29
30
31
README.md
1# PrintSpooler<a name="ZH-CN_TOPIC_0000001103330836"></a>
2
3- [简介](#section11660541593)
4 - [架构图](#section125101832114213)
5- [目录](#section161941989596)
6- [使用说明](#section123459000)
7- [相关仓](#section1371113476307)
8
9## 简介<a name="section11660541593"></a>
10
11PrintSpooler应用是OpenHarmony中预置的系统应用,为用户提供打印预览、发现和连接打印机、打印参数设置、下发打印任务以及打印任务状态的管理等功能。
12
13### 约束限制
14当前只支持对接支持ipp无驱动打印协议的打印机,需要单独安装驱动的打印机暂不支持对接。
15
16### 架构图<a name="section125101832114213"></a>
17
18![](figures/spooler_01.png)
19
20## 目录<a name="section161941989596"></a>
21
22```
23/applications/standard/print_spooler
24 ├── LICENSE # 许可文件
25 ├── common # 通用工具类目录
26 ├── entry # entry模块目录
27 ├── signature # 证书文件目录
28 ├── features # 子组件目录
29 │ ├── ippPrint # 局域网打印组件
30
31```
32###
33
34## 基础开发说明
35### 资源引用
36#### 定义资源文件
37- 在 `src/main/resources/`目录下,根据不同的资源类型,定义资源文件。
38
39 ```json
40 {
41 "name": "default_background_color",
42 "value": "#F1F3F5"
43 },
44 ```
45#### 引用资源
46- 在有对应page的ets文件中,可直接通过`$r()`引用。
47 ```` JavaScript
48 @Provide backgroundColor: Resource = $r('app.color.default_background_color');
49 ````
50## 典型接口的使用
51打印框架启动打印界面:
52
53 ```
54 std::string jobId = GetPrintJobId();
55 auto printJob = std::make_shared<PrintJob>();
56 if (printJob == nullptr) {
57 return E_PRINT_GENERIC_FAILURE;
58 }
59 printJob->SetFdList(fdList);
60 printJob->SetJobId(jobId);
61 printJob->SetJobState(PRINT_JOB_PREPARED);
62 AAFwk::Want want;
63 want.SetElementName(SPOOLER_BUNDLE_NAME, SPOOLER_ABILITY_NAME);
64 want.SetParam(LAUNCH_PARAMETER_JOB_ID, jobId);
65 want.SetParam(LAUNCH_PARAMETER_FILE_LIST, fileList);
66 BuildFDParam(fdList, want);
67 int32_t callerTokenId = static_cast<int32_t>(IPCSkeleton::GetCallingTokenID());
68 std::string callerPkg = DelayedSingleton<PrintBMSHelper>::GetInstance()->QueryCallerBundleName();
69 ingressPackage = callerPkg;
70 int32_t callerUid = IPCSkeleton::GetCallingUid();
71 int32_t callerPid = IPCSkeleton::GetCallingPid();
72 want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_TOKEN, callerTokenId);
73 want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_UID, callerUid);
74 want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_PID, callerPid);
75 want.SetParam(CALLER_PKG_NAME, callerPkg);
76 if (!StartAbility(want)) {
77 PRINT_HILOGE("Failed to start spooler ability");
78 return E_PRINT_SERVER_FAILURE;
79 }
80 ```
81
82## 打印能力指南
83### 支持预览界面显示预览图并根据设置动态刷新
84- `entry/src/main/ets/Common/Utils/FileUtil.ts `
85- 打开传入的图片uri,获得fd并生成imageSource
86 ```
87 import Fileio from '@ohos.file.fs';
88 import image from '@ohos.multimedia.image';
89
90 file = Fileio.openSync(uri, Constants.READ_WRITE);
91 let imageSource = image.createImageSource(file.fd);
92 imageArray.push(new FileModel(<number> file.fd, <string> file.fd, <string> uri,
93 <number> imageInfo.size.width, <number> imageInfo.size.height, imageSource));
94 ```
95
96- `entry/src/main/ets/pages/component/PreviewComponent.ets`
97- 动态变量imageSorce数组更新时,触发handleImage,调用parseImageSize,
98- fdToPixelMap生成pixelMap更新this.currentPixelMap,Image组件显示图片
99- 彩色、无边距等设置项使用组件属性完成动态刷新;
100- 其他设置项变化事件中调用parseImageSize调整或重新加载图片。
101 ```
102 @Link @Watch('handleImage') imageSources: Array<FileModel>
103 @State currentPixelMap: PixelMap = undefined;
104
105 Image(this.currentPixelMap).key('PreviewComponent_Image_currentPixelMap')
106 .width(this.canvasWidth).height(this.canvasHeight)
107 .backgroundColor($r('app.color.white'))
108 .objectFit(this.isBorderless?ImageFit.Cover:ImageFit.Contain)
109 .renderMode(this.colorMode === ColorMode.COLOR ? ImageRenderMode.Original : ImageRenderMode.Template)
110
111 handleImage(){
112 Log.info(TAG,'handleImage'+this.imageSources.length)
113 this.checkCanvasWidth()
114 this.parseImageSize(false);
115 }
116
117 parseImageSize(isRendered: boolean) {
118 this.originalIndex = this.printRange[this.currentIndex - 1]
119 this.currentImage = this.imageSources[this.originalIndex - 1];
120 if (CheckEmptyUtils.isEmpty(this.currentImage)){
121 return;
122 }
123 if (!isRendered) {
124 this.fdToPixelMap(this.currentImage.fd);
125 }
126 let width = this.currentImage.width
127 let height = this.currentImage.height
128 if(width > height) {
129 this.imageOrientation = PageDirection.LANDSCAPE //图片横向
130 } else {
131 this.imageOrientation = PageDirection.VERTICAL //图片竖向
132 }
133 this.updateCanvasSize()
134 }
135 ```
136### 支持打印任务管理,显示打印任务状态及错误信息
137-`entry/src/main/ets/pages/JobManagerPage.ets`
138-任务管理界面加载时,根据任务id创建本地任务,用@StorageLink动态监控打印任务队列变化,任务有更新后刷新界面显示;
139
140 ```
141 @StorageLink('JobQueue') jobQueue: Array<PrintJob> = new Array();
142
143 List() {
144 ForEach(this.jobQueue, (printJob:PrintJob)=>{
145 ListItem(){
146 printJobComponent({ mPrintJob: printJob});
147 }.key(`JobManagerPage_ListItem_${printJob.jobId}`)
148 }, printJob=>printJob.jobId)
149 }
150
151 aboutToAppear() {
152 this.abilityContext = GlobalThisHelper.getValue<common.UIAbilityContext>(GlobalThisStorageKey.KEY_JOB_MANAGER_ABILITY_CONTEXT)
153 let data = {
154 wantJobId : Constants.STRING_NONE
155 }
156 this.abilityContext.eventHub.emit(Constants.EVENT_GET_ABILITY_DATA, data);
157 this.jobId = data.wantJobId;
158 this.adapter = PrintAdapter.getInstance();
159 this.adapter.getPrintJobCtl().createPrintJob(this.jobId)
160 }
161 ```
162
163- `entry/src/main/ets/Controller/PrintJobController.ets`
164- 初始化时将本地任务队列存入AppStorage,建立和界面@StorageLink的关联;
165- 通过print.on接口,传入回调监听任务状态更新事件;
166- 状态更新时通过更新本地任务队列,@StorageLink动态刷新界面
167 ```
168 public init(): void {
169 AppStorageHelper.createValue<Array<PrintJob>>(this.getModel().mPrintJobs, AppStorageKeyName.JOB_QUEUE_NAME);
170 this.registerPrintJobCallback();
171 }
172
173 private registerPrintJobCallback(): void {
174 print.on('jobStateChange', this.onJobStateChanged);
175 }
176
177 private onJobStateChanged = (state: print.PrintJobState, job: print.PrintJob): void => {
178 if (state === null || job === null) {
179 Log.error(TAG, 'device state changed null data');
180 return;
181 }
182 this.deleteLocalSource(<number>state, <string>job.jobId);
183 switch (state) {
184 case PrintJobState.PRINT_JOB_PREPARED:
185 case PrintJobState.PRINT_JOB_QUEUED:
186 case PrintJobState.PRINT_JOB_RUNNING:
187 case PrintJobState.PRINT_JOB_BLOCKED:
188 case PrintJobState.PRINT_JOB_COMPLETED:
189 this.onPrintJobStateChange(job);
190 break;
191 default:
192 break;
193 }
194 };
195
196 private onPrintJobStateChange(job: print.PrintJob): void {
197 if (job === null) {
198 return;
199 }
200 this.getModel().printJobStateChange(job.jobId, job.jobState, job.jobSubState);
201 }
202 ```
203### 打印需支持mopria协议,支持p2p连接
204- `feature/ippPrint/src/main/ets/common/discovery/P2pDiscoveryChannel.ts`
205- 调用Wifi-P2p的接口发现周边的p2p打印机
206 ```
207 import wifi from '@ohos.wifi';
208
209 startDiscovery(callback: (found: boolean, peer: wifi.WifiP2pDevice) => void): void {
210 this.discoveryCallback = callback;
211 wifi.on('p2pPeerDeviceChange', this.updatePeerDevices);
212 this.startP2pDiscovery();
213 this.registerWifiCommonEvent();
214
215
216 this.discoverySleepTimer = setInterval(()=> {
217 Log.debug(TAG, 'native p2p service is sleep, start discovery');
218 this.startP2pDiscovery();
219 }, DISCOVERY_SLEEP);
220 }
221
222 private startP2pDiscovery(): void {
223 wifi.startDiscoverDevices();
224 wifi.getP2pPeerDevices().then((peers: wifi.WifiP2pDevice[]) => {
225 this.updatePeerDevices(peers);
226 });
227 }
228 ```
229- `feature/ippPrint/src/main/ets/common/connect/P2pPrinterConnection.ts`
230- 发现p2p打印机之后,执行连接,获取对端打印机的ip信息
231 ```
232 import wifi from '@ohos.wifi';
233
234 private startConnect(printer: DiscoveredPrinter): void {
235 Log.debug(TAG, 'connect to ' + CommonUtils.getSecurityMac(printer.getDeviceAddress()));
236 let config: wifi.WifiP2PConfig = this.configForPeer(printer);
237 this.mWifiModel.registerWifiP2pEvent(WifiModel.p2pConnectionChange, this.p2pConnectionChangeReceive);
238 this.mWifiModel.registerWifiP2pEvent(WifiModel.p2pPeerDeviceChange, this.p2pPeersChangeReceive);
239 let connectionOperation: boolean = this.mWifiModel.connectToPrinter(config);
240 if (!connectionOperation) {
241 Log.error(TAG, 'connection operation failed');
242 if (this.delayTimer !== undefined) {
243 clearTimeout(this.delayTimer);
244 }
245 this.mWifiModel.unregisterWifiP2pEvent(WifiModel.p2pConnectionChange, this.p2pConnectionChangeReceive);
246 this.mWifiModel.unregisterWifiP2pEvent(WifiModel.p2pPeerDeviceChange, this.p2pPeersChangeReceive);
247 this.mListener.onConnectionDelayed();
248 return;
249 }
250 Log.error(TAG, 'connection operation success');
251 }
252 ```
253- `feature/ippPrint/src/main/ets/common/napi/NativeApi.ts`
254- p2p打印机连接成功之后调用print_print_fwk的接口获取打印机支持的ipp协议能力,并调用print_print_fwk接口向cupsd服务配置一台无驱动打印机(支持Mopria协议)
255 ```
256 import print from '@ohos.print';
257
258 public getCapabilities(uri: string, printerName: string, getCapsCallback: (result) => void): void {
259 Log.debug(TAG, 'getCapabilities enter');
260 if (print === undefined) {
261 Log.error(TAG, 'print is undefined');
262 getCapsCallback(ERROR);
263 return;
264 }
265 Log.debug(TAG, 'getCapabilities start');
266 // 获取打印机的ipp打印能力
267 print.queryPrinterCapabilityByUri(uri).then((result) => {
268 Log.debug(TAG, 'nativeGetCapabilities result: ' + JSON.stringify(result));
269 this.setCupsPrinter(uri, this.removeSpaces(printerName));
270 getCapsCallback(result);
271 }).catch((error) => {
272 Log.error(TAG, 'nativeGetCapabilities error: ' + JSON.stringify(error));
273 getCapsCallback(ERROR);
274 });
275 Log.debug(TAG, 'getCapabilities end');
276 }
277
278 public setCupsPrinter(uri: string, name: string): void {
279 Log.debug(TAG, 'setCupsPrinter enter');
280 if (print === undefined) {
281 Log.error(TAG, 'print is undefined');
282 return;
283 }
284 // 向cupsd服务配置无驱动打印机
285 print.addPrinterToCups(uri, name).then((result) => {
286 Log.debug(TAG, 'nativeSetCupsPrinter result: ' + JSON.stringify(result));
287 }).catch((error) => {
288 Log.error(TAG, 'nativeSetCupsPrinter error: ' + JSON.stringify(error));
289 });
290 }
291 ```
292- 打印框架代码详见:[print_print_fwk](https://gitee.com/openharmony/print_print_fwk)
293
294## 签名打包
295### 签名
296#### 签名文件的获取
2971. 拷贝OpenHarmony标准版 工程的 OpenHarmony\signcenter_tool 目录到操作目录
2982. 标准版的签名文件下载路径:https://gitee.com/openharmony/signcenter_tool?_from=gitee_search。
2993. PrintSpooler 工程的 signature\spooler.p7b 到该目录下
300#### 签名文件的配置
301打开项目工程,选择 File → Project Structure
302
303![](figures/signature_1.png)
304
305选择Project → Signing Configs,将对应的签名文件配置如下,完成后点击Apply,再点击OK。
306密码为生成签名文件时的密码,如果使用默认的签名文件,则使用默认密码123456。
307
308![](figures/signature_2.png)
309
310## 安装、运行、调试
311## 应用安装
312配置 hdc:
313进入SDK目录中的toolchains文件夹下,获取文件路径:
314
315![](figures/screenshot-20210521-105407.png)
316
317> 注意,此处的hdc.exe如果版本较老,可能不能正常使用,需要获取新的hdc.exe文件
318> hdc命令介绍与下载详见:[hdc仓库地址](https://gitee.com/openharmony/developtools_hdc_standard)
319
320
321并将此路径配置到环境变量中:
322
323![](figures/screenshot-20210521-111223.png)
324
325重启电脑使环境变量生效
326
327连接开发板,打开cmd命令窗口,执行hdc list targets,弹出窗口如下:
328
329![](figures/cmd1.png)
330
331等待一段时间后,窗口出现如下打印,可回到输入 hdc list targets 的命令窗口继续操作:
332
333![](figures/cmd2.png)
334
335再次输入hdc list targets,出现如下结果,说明hdc连接成功
336
337![](figures/cmd3.png)
338
339获取读写权限:
340
341```
342hdc target mount
343```
344将签名好的 hap 包放入设备的 `/system/app/com.ohos.spooler` 目录下,并修改hap包的权限
345
346```
347hdc file send 本地路径 /system/app/com.ohos.spooler/hap包名称
348例如:hdc file send Spooler.hap /system/app/com.ohos.spooler/Spooler.hap
349```
350## 应用运行
351Spooler属于系统应用,在将签名的 hap 包放入 `/system/app/com.ohos.spooler` 目录后,重启系统,应用会自动拉起。
352```
353hdc shell
354reboot
355(不可以直接执行hdc reboot,命令是无效的)
356```
357> 注意,如果设备之前安装过系统应用,则需要执行如下两条命令清除设备中存储的应用信息才能够在设备重启的时候将我们装入设备的新 hap 包正常拉起。
358> ```
359> hdc shell rm -rf /data/misc_de/0/mdds/0/default/bundle_manager_service
360> hdc shell rm -rf /data/accounts
361> ```
362## 应用调试
363### log打印
364- 在程序中添加 log
365```JS
366import hilog from '@ohos.hilog';
367hilog.info(0x0001, "Spooler", "%{public}s World %{private}d", "hello", 3);
368```
369### log获取及过滤
370- log获取
371
372
373将log输出至文件
374```
375hdc shell hilog > 输出文件名称
376```
377
378例:
379在真实环境查看log,将全log输出到当前目录的hilog.log文件中
380```
381hdc shell hilog > hilog.log
382```
383
384- log过滤
385
386在命令行窗口中过滤log
387```
388hilog │ grep 过滤信息
389```
390
391例:过滤包含信息 Label 的 hilog
392```
393hilog │ grep Label
394```
395## 贡献代码
396### Fork 代码仓库
3971. 在码云上打开 PrintSpooler 代码仓库([仓库地址](https://gitee.com/openharmony/applications_print_spooler))。
398
3992. 点击仓库右上角的 Forked 按钮,在弹出的画面中,选择将仓库 fork 到哪里,点击确认。
400
4013. Fork 成功之后,会在自己的账号下看见 fork 的代码仓库。
402
403### 提交代码
4041. 访问我们自己在码云账号上 fork 的代码仓库,点击“克隆/下载”按钮,选择 SSH/HTTPS,点击“复制”按钮。
405
4062. 在本地新建 PrintSpooler 目录,在 PrintSpooler 目录中执行如下命令
407 ```
408 git clone 步骤1中复制的地址
409 ```
410
4113. 修改代码。
412
413 > 将代码引入工程,以及编译工程等相关内容请参见 **3. 代码使用** 部分的相关内容。
4144. 提交代码到 fork 仓库。
415 > 修改后的代码,首先执行 `git add` 命令,然后执行 `git commit` 命令与 `git push` 命令,将代码 push 到我们自己的 fork 仓中。
416 > 关于代码提交的这部分内容涉及 git 的使用,可以参照 [git官网](https://git-scm.com/) 的内容,在此不再赘述。
417
418### 发起 Pull Request (PR)
419在将代码提交到 fork 仓之后,我们可以通过发起 Pull Request(PR)的方式来为 OpenHarmony 的相关项目贡献代码。
420
4211. 打开 fork 仓库。选择 `Pull Requests` → `新建 Pull Request`
422
4232. 在 `新建 Pull Request` 画面填入标题与说明,点击 `创建` 按钮。
424
4253. 创建 Pull Request 完成。 PR 创建完成后,会有专门的代码审查人员对代码进行评审,评审通过之后会合入相应的代码库。
426