M3(批4): 图片/摇一摇/扫码/相机/定位 Provider
T-M3-07 PhotoProvider:getphoto 批量下载到资源根 photos/(file:// 白名单内) → 出站 getphoto[{pid,photourl}],复用 Downloader 并行
T-M3-08 ShakeProvider:startshake/SwitchShake/stopshake,@ohos.sensor 加速度合力阈值检测+节流,~1s 后出站 shakeEnd(空串),反馈震动
T-M3-11 ScanProvider:opensaoma → @kit.ScanKit 默认界面扫码(免权限) → 出站 getsaomaData(originalValue)
T-M3-12 CameraProvider:opencamera → @kit.CameraKit cameraPicker.pick(免权限) → 出站 getcameraaAddress(resultUri)
T-M3-09 LocationProvider:startlocation(连续/单次,用时申请权限)/getlocationinfo(同步) → geoLocationManager 定位+逆地理编码 →
出站 getlocationinfo(MaplocationInfo;错误码 0/-1未就绪/-2权限拒绝)
module.json5 增 ACCELEROMETER/LOCATION/APPROXIMATELY_LOCATION;组装根装配 5 者
devecocli build 通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
*/
|
||||
import { CapabilityProvider, EchoSampleProvider, RoomStubProvider, PayStubProvider,
|
||||
VibrateProvider, ClipboardProvider, AppSystemProvider, DeviceProvider,
|
||||
NetworkProvider, ShareProvider, LoginProvider } from 'feature_capabilities';
|
||||
NetworkProvider, ShareProvider, LoginProvider, PhotoProvider, ShakeProvider,
|
||||
ScanProvider, CameraProvider, LocationProvider } from 'feature_capabilities';
|
||||
|
||||
export function buildCapabilities(): CapabilityProvider[] {
|
||||
return [
|
||||
@@ -20,6 +21,11 @@ export function buildCapabilities(): CapabilityProvider[] {
|
||||
new NetworkProvider(), // getnetwork + 出站广播
|
||||
new ShareProvider(), // friendsSharetypeUrlToptitleDescript → sharesuccess(微信)
|
||||
new LoginProvider(), // accreditlogin → sharelogin(微信 code 模式,profile 待服务端)
|
||||
new PhotoProvider(), // getphoto 批量下载 → getphoto
|
||||
new ShakeProvider(), // startshake/SwitchShake/stopshake → shakeEnd
|
||||
new ScanProvider(), // opensaoma → getsaomaData
|
||||
new CameraProvider(), // opencamera → getcameraaAddress
|
||||
new LocationProvider(), // startlocation/getlocationinfo → getlocationinfo
|
||||
// —— 暂缓能力占位桩(§6.5)——
|
||||
new RoomStubProvider(),
|
||||
new PayStubProvider(),
|
||||
|
||||
@@ -67,6 +67,25 @@
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.GET_WIFI_INFO"
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.ACCELEROMETER"
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.LOCATION",
|
||||
"reason": "$string:reason_location",
|
||||
"usedScene": {
|
||||
"abilities": ["EntryAbility"],
|
||||
"when": "inuse"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.APPROXIMATELY_LOCATION",
|
||||
"reason": "$string:reason_location",
|
||||
"usedScene": {
|
||||
"abilities": ["EntryAbility"],
|
||||
"when": "inuse"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
{
|
||||
"name": "reason_internet",
|
||||
"value": "用于下载游戏资源与获取远程配置"
|
||||
},
|
||||
{
|
||||
"name": "reason_location",
|
||||
"value": "用于向游戏提供定位服务"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -16,6 +16,11 @@ export { DeviceProvider } from './src/main/ets/providers/DeviceProvider';
|
||||
export { NetworkProvider } from './src/main/ets/providers/NetworkProvider';
|
||||
export { ShareProvider } from './src/main/ets/providers/ShareProvider';
|
||||
export { LoginProvider } from './src/main/ets/providers/LoginProvider';
|
||||
export { PhotoProvider } from './src/main/ets/providers/PhotoProvider';
|
||||
export { ShakeProvider } from './src/main/ets/providers/ShakeProvider';
|
||||
export { ScanProvider } from './src/main/ets/providers/ScanProvider';
|
||||
export { CameraProvider } from './src/main/ets/providers/CameraProvider';
|
||||
export { LocationProvider } from './src/main/ets/providers/LocationProvider';
|
||||
|
||||
// 微信 SDK 封装(EntryAbility 处理回调 want 需用)
|
||||
export { WeChatApi, WX_APP_ID } from './src/main/ets/wx/WeChatApi';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 相机拍照能力(契约 §8.8/§9,T-M3-12)。@kit.CameraKit CameraPicker。
|
||||
* - opencamera:拉起系统相机拍照 → 出站 getcameraaAddress(拼写原样双 a,照片 uri 裸串)。
|
||||
* 系统相机选择器无需申请相机权限。
|
||||
*/
|
||||
import { camera, cameraPicker } from '@kit.CameraKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OutboundHandlers } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
export class CameraProvider implements CapabilityProvider {
|
||||
readonly name: string = 'camera';
|
||||
private readonly log: Logger = Logger.tag('CameraProvider');
|
||||
private bridge: BridgeController | undefined = undefined;
|
||||
private context: common.UIAbilityContext | undefined = undefined;
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
this.bridge = bridge;
|
||||
this.context = ctx.uiAbilityContext;
|
||||
bridge.registerHandler(InboundHandlers.OpenCamera, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.capture();
|
||||
});
|
||||
}
|
||||
|
||||
private capture(): void {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const profile: cameraPicker.PickerProfile = {
|
||||
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK,
|
||||
};
|
||||
cameraPicker.pick(ctx, [cameraPicker.PickerMediaType.PHOTO], profile)
|
||||
.then((result: cameraPicker.PickerResult) => {
|
||||
if (result.resultCode === 0 && result.resultUri !== '') {
|
||||
this.bridge?.callHandler(OutboundHandlers.GetCameraAddress, result.resultUri);
|
||||
} else {
|
||||
this.log.i(`camera canceled: resultCode=${result.resultCode}`);
|
||||
}
|
||||
})
|
||||
.catch((e: BusinessError) => {
|
||||
this.log.i(`camera failed: ${e.code} ${e.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* 定位能力(契约 §8.6/§9/§10.8,T-M3-09)。@kit.LocationKit geoLocationManager。
|
||||
* - startlocation:data="1" 连续 / 其他 单次。用时申请定位权限;定位成功逆地理编码后出站 getlocationinfo。
|
||||
* - getlocationinfo:同步返回最近一次 MaplocationInfo(未就绪 errorCode=-1 / 权限拒绝 -2)。
|
||||
* 需 module.json5 声明 LOCATION + APPROXIMATELY_LOCATION,运行时申请。
|
||||
*/
|
||||
import { geoLocationManager } from '@kit.LocationKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { common, Permissions } from '@kit.AbilityKit';
|
||||
import { PermissionGuard } from 'platform';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OutboundHandlers, MaplocationInfo } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
const LOC_PERMS: Permissions[] = ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION'];
|
||||
|
||||
export class LocationProvider implements CapabilityProvider {
|
||||
readonly name: string = 'location';
|
||||
private readonly log: Logger = Logger.tag('LocationProvider');
|
||||
private bridge: BridgeController | undefined = undefined;
|
||||
private context: common.UIAbilityContext | undefined = undefined;
|
||||
private lastInfo: MaplocationInfo | undefined = undefined;
|
||||
private continuousOn: boolean = false;
|
||||
private readonly onLocation: (loc: geoLocationManager.Location) => void;
|
||||
|
||||
constructor() {
|
||||
this.onLocation = (loc: geoLocationManager.Location) => this.handleLocation(loc);
|
||||
}
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
this.bridge = bridge;
|
||||
this.context = ctx.uiAbilityContext;
|
||||
bridge.registerHandler(InboundHandlers.StartLocation, (data: string, _cb: (resp: string) => void) => {
|
||||
this.start(data === '1');
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.GetLocationInfo, (_d: string, cb: (resp: string) => void) => {
|
||||
cb(JSON.stringify(this.lastInfo ?? this.errorInfo(-1)));
|
||||
});
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
this.stopContinuous();
|
||||
}
|
||||
|
||||
private async start(continuous: boolean): Promise<void> {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const granted: boolean = await PermissionGuard.request(ctx, LOC_PERMS);
|
||||
if (!granted) {
|
||||
this.lastInfo = this.errorInfo(-2);
|
||||
this.push(this.lastInfo);
|
||||
return;
|
||||
}
|
||||
if (continuous) {
|
||||
this.startContinuous();
|
||||
} else {
|
||||
this.requestSingle();
|
||||
}
|
||||
}
|
||||
|
||||
private requestSingle(): void {
|
||||
const request: geoLocationManager.CurrentLocationRequest = {
|
||||
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
|
||||
scenario: geoLocationManager.LocationRequestScenario.UNSET,
|
||||
};
|
||||
geoLocationManager.getCurrentLocation(request)
|
||||
.then((loc: geoLocationManager.Location) => this.handleLocation(loc))
|
||||
.catch((e: BusinessError) => {
|
||||
this.log.w(`getCurrentLocation failed: ${e.code} ${e.message}`);
|
||||
this.lastInfo = this.errorInfo(e.code);
|
||||
this.push(this.lastInfo);
|
||||
});
|
||||
}
|
||||
|
||||
private startContinuous(): void {
|
||||
if (this.continuousOn) {
|
||||
return;
|
||||
}
|
||||
const request: geoLocationManager.LocationRequest = {
|
||||
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
|
||||
scenario: geoLocationManager.LocationRequestScenario.UNSET,
|
||||
timeInterval: 5,
|
||||
distanceInterval: 0,
|
||||
maxAccuracy: 0,
|
||||
};
|
||||
try {
|
||||
geoLocationManager.on('locationChange', request, this.onLocation);
|
||||
this.continuousOn = true;
|
||||
} catch (e) {
|
||||
this.log.w(`on locationChange failed: ${(e as BusinessError).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private stopContinuous(): void {
|
||||
if (!this.continuousOn) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
geoLocationManager.off('locationChange', this.onLocation);
|
||||
} catch (e) {
|
||||
this.log.w(`off locationChange failed: ${(e as BusinessError).message}`);
|
||||
}
|
||||
this.continuousOn = false;
|
||||
}
|
||||
|
||||
/** 定位成功 → 逆地理编码(best-effort)→ 构建 MaplocationInfo → 出站。 */
|
||||
private handleLocation(loc: geoLocationManager.Location): void {
|
||||
const base: MaplocationInfo = this.baseInfo(loc);
|
||||
const req: geoLocationManager.ReverseGeoCodeRequest = {
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
maxItems: 1,
|
||||
};
|
||||
geoLocationManager.getAddressesFromLocation(req)
|
||||
.then((addrs: geoLocationManager.GeoAddress[]) => {
|
||||
if (addrs.length > 0) {
|
||||
this.fillAddress(base, addrs[0]);
|
||||
}
|
||||
this.lastInfo = base;
|
||||
this.push(base);
|
||||
})
|
||||
.catch((e: BusinessError) => {
|
||||
this.log.i(`reverse geocode failed (${e.code}); push coords only`);
|
||||
this.lastInfo = base;
|
||||
this.push(base);
|
||||
});
|
||||
}
|
||||
|
||||
private baseInfo(loc: geoLocationManager.Location): MaplocationInfo {
|
||||
return {
|
||||
latitude: loc.latitude,
|
||||
longitude: loc.longitude,
|
||||
address: '', country: '', province: '', city: '', district: '', street: '',
|
||||
cityCode: '', streetNum: '', adCode: '', aoiName: '',
|
||||
accuracy: loc.accuracy,
|
||||
locationType: 0,
|
||||
errorCode: 0,
|
||||
errorMsg: '',
|
||||
};
|
||||
}
|
||||
|
||||
private fillAddress(info: MaplocationInfo, a: geoLocationManager.GeoAddress): void {
|
||||
info.address = a.placeName ?? '';
|
||||
info.country = a.countryName ?? '';
|
||||
info.province = a.administrativeArea ?? '';
|
||||
info.city = a.locality ?? '';
|
||||
info.district = a.subLocality ?? '';
|
||||
info.street = a.roadName ?? '';
|
||||
}
|
||||
|
||||
private errorInfo(code: number): MaplocationInfo {
|
||||
return {
|
||||
latitude: 0, longitude: 0,
|
||||
address: '', country: '', province: '', city: '', district: '', street: '',
|
||||
cityCode: '', streetNum: '', adCode: '', aoiName: '',
|
||||
accuracy: 0, locationType: 0,
|
||||
errorCode: code,
|
||||
errorMsg: code === -2 ? 'permission denied' : (code === -1 ? 'not ready' : 'location error'),
|
||||
};
|
||||
}
|
||||
|
||||
private push(info: MaplocationInfo): void {
|
||||
this.bridge?.callHandler(OutboundHandlers.GetLocationInfo, JSON.stringify(info));
|
||||
if (!this.continuousOn) {
|
||||
this.stopContinuous();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 图片下载能力(契约 §8.1/§9,T-M3-07)。复用 platform Downloader。
|
||||
* - getphoto:data=JSON 数组 [{pid, photourl(远程)}],批量下载到本地。
|
||||
* - 出站 getphoto:[{pid, photourl(本地 file://)}],全部下载完成后推送。
|
||||
* 落盘到资源根 photos/(在 file:// 跨域白名单内,H5 可经 <img> 引用)。
|
||||
*/
|
||||
import { Downloader } from 'platform';
|
||||
import { ResourceManager } from 'domain_resource';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OutboundHandlers, SavephotoURLBean } from 'contracts';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
export class PhotoProvider implements CapabilityProvider {
|
||||
readonly name: string = 'photo';
|
||||
private readonly log: Logger = Logger.tag('PhotoProvider');
|
||||
private bridge: BridgeController | undefined = undefined;
|
||||
private context: common.UIAbilityContext | undefined = undefined;
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
this.bridge = bridge;
|
||||
this.context = ctx.uiAbilityContext;
|
||||
bridge.registerHandler(InboundHandlers.GetPhoto, (data: string, _cb: (resp: string) => void) => {
|
||||
this.downloadAll(data);
|
||||
});
|
||||
}
|
||||
|
||||
private downloadAll(data: string): void {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
let list: SavephotoURLBean[];
|
||||
try {
|
||||
list = JSON.parse(data) as SavephotoURLBean[];
|
||||
} catch (e) {
|
||||
this.log.w(`getphoto bad json: ${(e as Error).message}`);
|
||||
return;
|
||||
}
|
||||
const dir: string = `${ResourceManager.resourceRoot(ctx)}/photos`;
|
||||
const tasks: Promise<SavephotoURLBean>[] = list.map((item: SavephotoURLBean) => this.downloadOne(item, dir));
|
||||
Promise.all(tasks).then((results: SavephotoURLBean[]) => {
|
||||
this.bridge?.callHandler(OutboundHandlers.GetPhoto, JSON.stringify(results));
|
||||
}).catch((e: Error) => {
|
||||
this.log.w(`getphoto failed: ${e.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
private async downloadOne(item: SavephotoURLBean, dir: string): Promise<SavephotoURLBean> {
|
||||
const local: string = `${dir}/${item.pid}${this.guessExt(item.photourl)}`;
|
||||
try {
|
||||
await Downloader.download(item.photourl, local);
|
||||
return { pid: item.pid, photourl: `file://${local}` };
|
||||
} catch (e) {
|
||||
this.log.w(`download photo ${item.pid} failed: ${(e as Error).message}`);
|
||||
return { pid: item.pid, photourl: '' };
|
||||
}
|
||||
}
|
||||
|
||||
private guessExt(url: string): string {
|
||||
const q: number = url.indexOf('?');
|
||||
const clean: string = q >= 0 ? url.substring(0, q) : url;
|
||||
const dot: number = clean.lastIndexOf('.');
|
||||
const slash: number = clean.lastIndexOf('/');
|
||||
if (dot > slash && dot >= 0 && clean.length - dot <= 5) {
|
||||
return clean.substring(dot);
|
||||
}
|
||||
return '.jpg';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 扫码能力(契约 §8.8/§9,T-M3-11)。@kit.ScanKit 默认界面扫码。
|
||||
* - opensaoma(拼写原样):拉起系统扫码界面 → 出站 getsaomaData(扫码结果裸串)。
|
||||
* 默认界面扫码由系统提供,无需申请相机权限。
|
||||
*/
|
||||
import { scanBarcode } from '@kit.ScanKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OutboundHandlers } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
export class ScanProvider implements CapabilityProvider {
|
||||
readonly name: string = 'scan';
|
||||
private readonly log: Logger = Logger.tag('ScanProvider');
|
||||
private bridge: BridgeController | undefined = undefined;
|
||||
private context: common.UIAbilityContext | undefined = undefined;
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
this.bridge = bridge;
|
||||
this.context = ctx.uiAbilityContext;
|
||||
bridge.registerHandler(InboundHandlers.OpenSaoma, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.scan();
|
||||
});
|
||||
}
|
||||
|
||||
private scan(): void {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
scanBarcode.startScanForResult(ctx)
|
||||
.then((result: scanBarcode.ScanResult) => {
|
||||
this.bridge?.callHandler(OutboundHandlers.GetSaomaData, result.originalValue ?? '');
|
||||
})
|
||||
.catch((e: BusinessError) => {
|
||||
// 用户取消或失败:不推(H5 按"无事发生"处理),仅日志
|
||||
this.log.i(`scan canceled/failed: ${e.code} ${e.message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 摇一摇能力(契约 §8.5/§9,T-M3-08)。@kit.SensorServiceKit 加速度计。
|
||||
* - startshake:开始监听;检测到摇动后约 1 秒出站 shakeEnd(空串)。
|
||||
* - SwitchShake:data="1" 开声音/震动反馈 / 其他关。
|
||||
* - stopshake:停止监听。
|
||||
* 需 module.json5 声明 ohos.permission.ACCELEROMETER。
|
||||
*/
|
||||
import { sensor } from '@kit.SensorServiceKit';
|
||||
import { vibrator } from '@kit.SensorServiceKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OutboundHandlers } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
/** 摇动阈值(合加速度 m/s²;静止约 9.8,摇动易超 20)。 */
|
||||
const SHAKE_THRESHOLD: number = 20;
|
||||
/** 上报周期 100ms。 */
|
||||
const INTERVAL_NS: number = 100000000;
|
||||
/** shakeEnd 延迟(约 1 秒)。 */
|
||||
const SHAKE_END_DELAY: number = 1000;
|
||||
|
||||
export class ShakeProvider implements CapabilityProvider {
|
||||
readonly name: string = 'shake';
|
||||
private readonly log: Logger = Logger.tag('ShakeProvider');
|
||||
private bridge: BridgeController | undefined = undefined;
|
||||
private listening: boolean = false;
|
||||
private feedbackOn: boolean = false;
|
||||
private pending: boolean = false;
|
||||
private readonly onAccel: (data: sensor.AccelerometerResponse) => void;
|
||||
|
||||
constructor() {
|
||||
this.onAccel = (data: sensor.AccelerometerResponse) => this.handleAccel(data);
|
||||
}
|
||||
|
||||
register(bridge: BridgeController, _ctx: CapabilityContext): void {
|
||||
this.bridge = bridge;
|
||||
bridge.registerHandler(InboundHandlers.StartShake, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.start();
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.SwitchShake, (data: string, _cb: (resp: string) => void) => {
|
||||
this.feedbackOn = data === '1';
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.StopShake, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.stop();
|
||||
});
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
private start(): void {
|
||||
if (this.listening) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sensor.on(sensor.SensorId.ACCELEROMETER, this.onAccel, { interval: INTERVAL_NS });
|
||||
this.listening = true;
|
||||
} catch (e) {
|
||||
this.log.w(`sensor.on failed: ${(e as BusinessError).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private stop(): void {
|
||||
if (!this.listening) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sensor.off(sensor.SensorId.ACCELEROMETER, this.onAccel);
|
||||
} catch (e) {
|
||||
this.log.w(`sensor.off failed: ${(e as BusinessError).message}`);
|
||||
}
|
||||
this.listening = false;
|
||||
}
|
||||
|
||||
private handleAccel(data: sensor.AccelerometerResponse): void {
|
||||
if (this.pending) {
|
||||
return;
|
||||
}
|
||||
const magnitude: number = Math.sqrt(data.x * data.x + data.y * data.y + data.z * data.z);
|
||||
if (magnitude < SHAKE_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
// 摇动触发:节流;可选反馈震动;约 1 秒后出站 shakeEnd
|
||||
this.pending = true;
|
||||
if (this.feedbackOn) {
|
||||
vibrator.startVibration({ type: 'time', duration: 200 }, { usage: 'notification' })
|
||||
.catch((e: BusinessError) => this.log.w(`shake vibrate failed: ${e.message}`));
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.bridge?.callHandler(OutboundHandlers.ShakeEnd, '');
|
||||
this.pending = false;
|
||||
}, SHAKE_END_DELAY);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user