M3(批1): 占位桩 + 震动 + 剪贴板 + 系统能力 Provider
T-M3-15 RoomStubProvider/PayStubProvider:createRoom/exitRoom/getVideoinfo/DragViewvideoIsshow、
paybrowser/getGameplay 全 void 空实现+日志,绝不触发出站(§6.5 安全网)
T-M3-04 VibrateProvider:vibrator/repeatvibrator/canclevibrator(@ohos.vibrator,重复用递归 setTimeout)
T-M3-05 ClipboardProvider:gameCopytext/gamepastetext(@ohos.pasteboard,读取经 responseCallback 回执)
T-M3-01 AppSystemProvider:orientation(window.setPreferredOrientation 横/竖)、browser(隐式 want)、
finsh(terminateSelf)、openApplyDownloadpath(按包名启动失败回退浏览器)、notification(空)
组装根 buildCapabilities 装配上述 + 桩;module.json5 增 VIBRATE 权限
devecocli build 通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,16 +5,20 @@
|
||||
* M2:仅示例能力(验证插件模式)。M3:替换为全部真实 Provider + 占位桩
|
||||
* (RoomStubProvider/PayStubProvider,§6.5),契约 handler 名/数量不变。
|
||||
*/
|
||||
import { CapabilityProvider, EchoSampleProvider } from 'feature_capabilities';
|
||||
import { CapabilityProvider, EchoSampleProvider, RoomStubProvider, PayStubProvider,
|
||||
VibrateProvider, ClipboardProvider, AppSystemProvider } from 'feature_capabilities';
|
||||
|
||||
export function buildCapabilities(): CapabilityProvider[] {
|
||||
return [
|
||||
new EchoSampleProvider(),
|
||||
// —— M3 在此装配(顺序无关,互不依赖)——
|
||||
// new ShareProvider(), new LoginProvider(), new LocationProvider(), new AudioProvider(),
|
||||
// new ShakeProvider(), new DeviceProvider(), new ClipboardProvider(), new NetworkProvider(),
|
||||
// new VibrateProvider(), new ScanProvider(), new CameraProvider(), new PhotoProvider(),
|
||||
// new NavProvider(), new AppSystemProvider(),
|
||||
// new RoomStubProvider(), new PayStubProvider(), // 暂缓能力占位桩(§6.5)
|
||||
new EchoSampleProvider(), // M2 回显样板(M3 全部 Provider 就位后可移除)
|
||||
// —— M3 已落地能力(顺序无关,互不依赖)——
|
||||
new AppSystemProvider(), // orientation/browser/finsh/openApplyDownloadpath/notification
|
||||
new VibrateProvider(), // vibrator/repeatvibrator/canclevibrator
|
||||
new ClipboardProvider(), // gameCopytext/gamepastetext
|
||||
// —— 暂缓能力占位桩(§6.5)——
|
||||
new RoomStubProvider(),
|
||||
new PayStubProvider(),
|
||||
// —— M3 待落地:DeviceProvider/NetworkProvider/ShakeProvider/LocationProvider/AudioProvider/
|
||||
// ScanProvider/CameraProvider/PhotoProvider/NavProvider/ShareProvider/LoginProvider ——
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
"abilities": ["EntryAbility"],
|
||||
"when": "inuse"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.VIBRATE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,5 +4,11 @@
|
||||
export { CapabilityProvider, CapabilityContext } from './src/main/ets/core/CapabilityProvider';
|
||||
export { CapabilityRegistrar } from './src/main/ets/core/CapabilityRegistrar';
|
||||
|
||||
// 示例能力(M2 脚手架,M3 替换为真实 Provider)
|
||||
// 示例能力(M2 脚手架,M3 逐步替换为真实 Provider)
|
||||
export { EchoSampleProvider } from './src/main/ets/providers/EchoSampleProvider';
|
||||
|
||||
// —— M3 能力 Provider ——
|
||||
export { RoomStubProvider, PayStubProvider } from './src/main/ets/providers/StubProviders';
|
||||
export { VibrateProvider } from './src/main/ets/providers/VibrateProvider';
|
||||
export { ClipboardProvider } from './src/main/ets/providers/ClipboardProvider';
|
||||
export { AppSystemProvider } from './src/main/ets/providers/AppSystemProvider';
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 系统/应用能力(契约 §8.4/§8.8/§8.10,T-M3-01)。
|
||||
* - orientation:data="1" 横屏 / 其他 竖屏(窗口动态切换,§7.4 保留 H5 显式方向控制)。
|
||||
* - browser:data=URL,隐式 want 拉起系统浏览器。
|
||||
* - finsh(拼写原样):退出当前 Ability。
|
||||
* - openApplyDownloadpath:data=JSON{packagename,downloadpath},按包名启动 App,失败则浏览器打开下载地址。
|
||||
* - notification:空实现(契约标注未落地)。
|
||||
*/
|
||||
import { common, Want } from '@kit.AbilityKit';
|
||||
import { window } from '@kit.ArkUI';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers, OpenApplyDownloadReq } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
export class AppSystemProvider implements CapabilityProvider {
|
||||
readonly name: string = 'appsystem';
|
||||
private readonly log: Logger = Logger.tag('AppSystemProvider');
|
||||
private context: common.UIAbilityContext | undefined = undefined;
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
this.context = ctx.uiAbilityContext;
|
||||
bridge.registerHandler(InboundHandlers.Orientation, (data: string, _cb: (resp: string) => void) => {
|
||||
this.setOrientation(data === '1');
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.Browser, (data: string, _cb: (resp: string) => void) => {
|
||||
this.openBrowser(data);
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.Finsh, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.context?.terminateSelf()
|
||||
.catch((e: BusinessError) => this.log.w(`terminateSelf failed: ${e.code} ${e.message}`));
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.OpenApplyDownloadPath, (data: string, _cb: (resp: string) => void) => {
|
||||
this.openApply(data);
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.Notification, (_d: string, _cb: (resp: string) => void) => {
|
||||
// 契约标注:空实现,未落地
|
||||
});
|
||||
}
|
||||
|
||||
/** 动态设方向:横屏 LANDSCAPE / 竖屏 PORTRAIT(对齐 Android setRequestedOrientation)。 */
|
||||
private setOrientation(landscape: boolean): void {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
const target: window.Orientation = landscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT;
|
||||
window.getLastWindow(ctx)
|
||||
.then((win: window.Window) => win.setPreferredOrientation(target))
|
||||
.catch((e: BusinessError) => this.log.w(`setOrientation failed: ${e.code} ${e.message}`));
|
||||
}
|
||||
|
||||
private openBrowser(url: string): void {
|
||||
if (this.context === undefined || url === '') {
|
||||
return;
|
||||
}
|
||||
const want: Want = {
|
||||
action: 'ohos.want.action.viewData',
|
||||
entities: ['entity.system.browsable'],
|
||||
uri: url,
|
||||
};
|
||||
this.context.startAbility(want)
|
||||
.catch((e: BusinessError) => this.log.w(`openBrowser failed: ${e.code} ${e.message}`));
|
||||
}
|
||||
|
||||
private openApply(data: string): void {
|
||||
const ctx = this.context;
|
||||
if (ctx === undefined) {
|
||||
return;
|
||||
}
|
||||
let req: OpenApplyDownloadReq;
|
||||
try {
|
||||
req = JSON.parse(data) as OpenApplyDownloadReq;
|
||||
} catch (e) {
|
||||
this.log.w(`openApply bad json: ${(e as Error).message}`);
|
||||
return;
|
||||
}
|
||||
// 按包名启动 App;失败回退浏览器打开下载地址
|
||||
ctx.startAbility({ bundleName: req.packagename } as Want)
|
||||
.catch((e: BusinessError) => {
|
||||
this.log.i(`start app ${req.packagename} failed (${e.code}); fallback to download page`);
|
||||
this.openBrowser(req.downloadpath);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 剪贴板能力(契约 §8.4,T-M3-05)。@kit.BasicServicesKit pasteboard。
|
||||
* - gameCopytext:data=文本,写入系统剪贴板。
|
||||
* - gamepastetext:读取剪贴板内容,经 responseCallback 返回(getData 异步,回调延后触发,对 H5 仍是同步回执语义)。
|
||||
* 前台应用读取自身剪贴板无需声明权限;失败回空串(H5 不卡死)。
|
||||
*/
|
||||
import { pasteboard } from '@kit.BasicServicesKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
export class ClipboardProvider implements CapabilityProvider {
|
||||
readonly name: string = 'clipboard';
|
||||
private readonly log: Logger = Logger.tag('ClipboardProvider');
|
||||
|
||||
register(bridge: BridgeController, _ctx: CapabilityContext): void {
|
||||
bridge.registerHandler(InboundHandlers.GameCopyText, (data: string, _cb: (resp: string) => void) => {
|
||||
this.copy(data);
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.GamePasteText, (_d: string, cb: (resp: string) => void) => {
|
||||
this.paste(cb);
|
||||
});
|
||||
}
|
||||
|
||||
private copy(text: string): void {
|
||||
try {
|
||||
const data: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text);
|
||||
pasteboard.getSystemPasteboard().setData(data)
|
||||
.catch((e: BusinessError) => this.log.w(`setData failed: ${e.code} ${e.message}`));
|
||||
} catch (e) {
|
||||
this.log.w(`copy failed: ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private paste(cb: (resp: string) => void): void {
|
||||
pasteboard.getSystemPasteboard().getData()
|
||||
.then((data: pasteboard.PasteData) => {
|
||||
cb(data.getPrimaryText() ?? '');
|
||||
})
|
||||
.catch((e: BusinessError) => {
|
||||
this.log.w(`getData failed: ${e.code} ${e.message}`);
|
||||
cb('');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 暂缓能力占位桩(框架 §6.5)。本期视频房(声网)、支付不集成,但 handler 必须"留空注册"——
|
||||
* 否则 H5 调用行为不确定,若 H5 传了 responseCallback 还可能永久挂起。
|
||||
*
|
||||
* 规则:
|
||||
* - void 型 handler:空实现 + 日志,**绝不**调用任何 callHandler 出站。
|
||||
* - 有同步返回的:回安全默认值(本桩涉及的均为 void 型,无同步返回)。
|
||||
* - 绝不触发该能力出站(不 callHandler getVideoinfo / PayuserPaytypePaystate)。
|
||||
* 未来换回真实 RoomProvider/PayProvider 时,契约 handler 名/数量不变,对 H5 零改动。
|
||||
*/
|
||||
import { BridgeController, BridgeHandler } from 'feature_bridge';
|
||||
import { InboundHandlers } from 'contracts';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
/** 视频房占位桩(声网,§6.5)。 */
|
||||
export class RoomStubProvider implements CapabilityProvider {
|
||||
readonly name: string = 'room(stub)';
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
const noop: BridgeHandler = (_d: string, _cb: (resp: string) => void) => {
|
||||
ctx.log.i('room capability not integrated (stub)');
|
||||
};
|
||||
bridge.registerHandler(InboundHandlers.CreateRoom, noop);
|
||||
bridge.registerHandler(InboundHandlers.ExitRoom, noop);
|
||||
bridge.registerHandler(InboundHandlers.GetVideoInfo, noop);
|
||||
bridge.registerHandler(InboundHandlers.DragViewVideoIsShow, noop);
|
||||
}
|
||||
}
|
||||
|
||||
/** 支付占位桩(本期支付暂缓,§6.5)。 */
|
||||
export class PayStubProvider implements CapabilityProvider {
|
||||
readonly name: string = 'pay(stub)';
|
||||
|
||||
register(bridge: BridgeController, ctx: CapabilityContext): void {
|
||||
const noop: BridgeHandler = (_d: string, _cb: (resp: string) => void) => {
|
||||
ctx.log.i('pay capability not integrated (stub)');
|
||||
};
|
||||
bridge.registerHandler(InboundHandlers.PayBrowser, noop);
|
||||
bridge.registerHandler(InboundHandlers.GetGameplay, noop);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 震动能力(契约 §8.4,T-M3-04)。@kit.SensorServiceKit vibrator。
|
||||
* - vibrator:data=毫秒裸串,单次震动。
|
||||
* - repeatvibrator:data="1" 重复 / 其他停止重复。
|
||||
* - canclevibrator(拼写原样):取消全部震动。
|
||||
* 需 module.json5 声明 ohos.permission.VIBRATE。
|
||||
*/
|
||||
import { vibrator } from '@kit.SensorServiceKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { BridgeController } from 'feature_bridge';
|
||||
import { InboundHandlers } from 'contracts';
|
||||
import { Logger } from 'common';
|
||||
import { CapabilityContext, CapabilityProvider } from '../core/CapabilityProvider';
|
||||
|
||||
const DEFAULT_DURATION: number = 1000;
|
||||
|
||||
export class VibrateProvider implements CapabilityProvider {
|
||||
readonly name: string = 'vibrate';
|
||||
private readonly log: Logger = Logger.tag('VibrateProvider');
|
||||
private lastDuration: number = DEFAULT_DURATION;
|
||||
private repeating: boolean = false;
|
||||
private repeatTimer: number = -1;
|
||||
|
||||
register(bridge: BridgeController, _ctx: CapabilityContext): void {
|
||||
bridge.registerHandler(InboundHandlers.Vibrator, (data: string, _cb: (resp: string) => void) => {
|
||||
this.vibrateOnce(this.parseMs(data));
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.RepeatVibrator, (data: string, _cb: (resp: string) => void) => {
|
||||
if (data === '1') {
|
||||
this.startRepeat();
|
||||
} else {
|
||||
this.stopRepeat();
|
||||
}
|
||||
});
|
||||
bridge.registerHandler(InboundHandlers.CancleVibrator, (_d: string, _cb: (resp: string) => void) => {
|
||||
this.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
private parseMs(data: string): number {
|
||||
const n: number = parseInt(data, 10);
|
||||
return isNaN(n) || n <= 0 ? DEFAULT_DURATION : n;
|
||||
}
|
||||
|
||||
private vibrateOnce(ms: number): void {
|
||||
this.lastDuration = ms;
|
||||
vibrator.startVibration({ type: 'time', duration: ms }, { usage: 'notification' })
|
||||
.catch((e: BusinessError) => this.log.w(`startVibration failed: ${e.code} ${e.message}`));
|
||||
}
|
||||
|
||||
private startRepeat(): void {
|
||||
this.repeating = true;
|
||||
this.loop();
|
||||
}
|
||||
|
||||
private loop(): void {
|
||||
if (!this.repeating) {
|
||||
return;
|
||||
}
|
||||
this.vibrateOnce(this.lastDuration);
|
||||
// 当次震动结束后再触发下一次(间隔取等长),形成重复,直到取消
|
||||
this.repeatTimer = setTimeout(() => this.loop(), this.lastDuration * 2);
|
||||
}
|
||||
|
||||
private stopRepeat(): void {
|
||||
this.repeating = false;
|
||||
if (this.repeatTimer !== -1) {
|
||||
clearTimeout(this.repeatTimer);
|
||||
this.repeatTimer = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private cancel(): void {
|
||||
this.stopRepeat();
|
||||
vibrator.stopVibration().catch((e: BusinessError) => this.log.w(`stopVibration failed: ${e.code} ${e.message}`));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user