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 + 占位桩
|
* M2:仅示例能力(验证插件模式)。M3:替换为全部真实 Provider + 占位桩
|
||||||
* (RoomStubProvider/PayStubProvider,§6.5),契约 handler 名/数量不变。
|
* (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[] {
|
export function buildCapabilities(): CapabilityProvider[] {
|
||||||
return [
|
return [
|
||||||
new EchoSampleProvider(),
|
new EchoSampleProvider(), // M2 回显样板(M3 全部 Provider 就位后可移除)
|
||||||
// —— M3 在此装配(顺序无关,互不依赖)——
|
// —— M3 已落地能力(顺序无关,互不依赖)——
|
||||||
// new ShareProvider(), new LoginProvider(), new LocationProvider(), new AudioProvider(),
|
new AppSystemProvider(), // orientation/browser/finsh/openApplyDownloadpath/notification
|
||||||
// new ShakeProvider(), new DeviceProvider(), new ClipboardProvider(), new NetworkProvider(),
|
new VibrateProvider(), // vibrator/repeatvibrator/canclevibrator
|
||||||
// new VibrateProvider(), new ScanProvider(), new CameraProvider(), new PhotoProvider(),
|
new ClipboardProvider(), // gameCopytext/gamepastetext
|
||||||
// new NavProvider(), new AppSystemProvider(),
|
// —— 暂缓能力占位桩(§6.5)——
|
||||||
// new RoomStubProvider(), new PayStubProvider(), // 暂缓能力占位桩(§6.5)
|
new RoomStubProvider(),
|
||||||
|
new PayStubProvider(),
|
||||||
|
// —— M3 待落地:DeviceProvider/NetworkProvider/ShakeProvider/LocationProvider/AudioProvider/
|
||||||
|
// ScanProvider/CameraProvider/PhotoProvider/NavProvider/ShareProvider/LoginProvider ——
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,9 @@
|
|||||||
"abilities": ["EntryAbility"],
|
"abilities": ["EntryAbility"],
|
||||||
"when": "inuse"
|
"when": "inuse"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ohos.permission.VIBRATE"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,11 @@
|
|||||||
export { CapabilityProvider, CapabilityContext } from './src/main/ets/core/CapabilityProvider';
|
export { CapabilityProvider, CapabilityContext } from './src/main/ets/core/CapabilityProvider';
|
||||||
export { CapabilityRegistrar } from './src/main/ets/core/CapabilityRegistrar';
|
export { CapabilityRegistrar } from './src/main/ets/core/CapabilityRegistrar';
|
||||||
|
|
||||||
// 示例能力(M2 脚手架,M3 替换为真实 Provider)
|
// 示例能力(M2 脚手架,M3 逐步替换为真实 Provider)
|
||||||
export { EchoSampleProvider } from './src/main/ets/providers/EchoSampleProvider';
|
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