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:
lanterngamescn
2026-06-25 19:59:04 +08:00
parent 841a964693
commit 9dc3e2ddf7
9 changed files with 463 additions and 1 deletions
@@ -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();
}
}
}