Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

517 lines
19 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ActivityInstance, AppId, OSState } from './types';
import type { ActivityResult, AppIntentFilter, IntentPayload } from './types/manifest';
import { SIMULATOR_CONFIG } from './data';
import PackageManagerService from './PackageManagerService';
import { getActiveTask, getTaskTopActivity } from './taskUtils';
import { AppNavigatorRegistry } from './AppNavigatorRegistry';
type ChooserListener = (state: IntentChooserState) => void;
export interface IntentChooserState {
open: boolean;
intent: IntentPayload | null;
matches: { appId: AppId; filter: AppIntentFilter }[];
}
export interface StartActivityDeps {
getState: () => OSState;
nextActivityId: () => string;
allocRequestCode: (callerActivityId: string, callback: (result: ActivityResult) => void) => number;
pushActivity: (taskId: string, activity: ActivityInstance) => void;
navigateToActivity: (activityId: string, route: string, opts?: { fallbackAppId?: AppId; replace?: boolean }) => void;
}
export interface StartActivityPlainDeps {
getState: () => OSState;
nextActivityId: () => string;
pushActivity: (taskId: string, activity: ActivityInstance) => void;
popActivity: (taskId: string) => void;
navigateToActivity: (activityId: string, route: string, opts?: { fallbackAppId?: AppId; replace?: boolean }) => void;
launchApp: (appId: AppId) => void;
markExternalRoute: (appId: AppId) => void;
setActivityIntent: (taskId: string, activityId: string, intent: IntentPayload) => void;
}
const chooserListeners = new Set<ChooserListener>();
let chooserResolver: ((appId: AppId | null) => void) | null = null;
let chooserState: IntentChooserState = {
open: false,
intent: null,
matches: [],
};
function buildIntentDebugStack(tag: string): string {
try {
throw new Error(tag);
} catch (error) {
if (!(error instanceof Error) || !error.stack) return 'n/a';
return error.stack
.split('\n')
.slice(1, 5)
.map((line) => line.trim())
.join(' <- ');
}
}
function emitChooser() {
for (const listener of chooserListeners) listener(chooserState);
}
function setChooser(next: IntentChooserState) {
chooserState = next;
emitChooser();
}
function matchesType(filterType: string | undefined, intentType: string | undefined) {
if (!filterType) return true;
if (!intentType) return false;
if (filterType.endsWith('/*')) return intentType.startsWith(filterType.slice(0, -1));
return filterType === intentType;
}
function matchesScheme(filterScheme: string | undefined, intentScheme: string | undefined) {
if (!filterScheme) return true;
return filterScheme === intentScheme;
}
function pickTargetFilter(appId: AppId, intent: IntentPayload) {
const targetManifest = PackageManagerService.getPackageInfo(appId);
const filters = targetManifest?.intentFilters ?? [];
return filters.find(
(f) => f.action === intent.action
&& matchesScheme(f.scheme, intent.scheme)
&& matchesType(f.type, intent.type),
);
}
function ensureCallerQueryVisible(callerAppId: AppId, intent: IntentPayload) {
const callerManifest = PackageManagerService.getPackageInfo(callerAppId);
if (!callerManifest) return;
const queries = callerManifest.queries ?? [];
const queryOk = queries.some((q) =>
q.action === intent.action
&& matchesScheme(q.scheme, intent.scheme)
&& matchesType(q.type, intent.type),
);
if (!queryOk) {
console.warn(
`[OS] startActivityForResult: ${callerAppId} does not declare queries for action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'}`
);
}
}
function requestIntentChoice(
intent: IntentPayload,
matches: { appId: AppId; filter: AppIntentFilter }[],
): Promise<AppId | null> {
return new Promise((resolve) => {
if (chooserResolver) {
resolve(null);
return;
}
chooserResolver = resolve;
setChooser({ open: true, intent, matches });
});
}
function launchResolvedIntentForResult(
appId: AppId,
callerAppId: AppId,
intent: IntentPayload,
callback: (result: ActivityResult) => void,
deps: StartActivityDeps,
): boolean {
if (!PackageManagerService.isInstalled(appId)) {
console.error(`[OS] startActivityForResult: Invalid appId: ${appId}`);
return false;
}
ensureCallerQueryVisible(callerAppId, intent);
const targetFilter = pickTargetFilter(appId, intent);
if (!targetFilter && !intent.route) {
console.error(
`[OS] startActivityForResult: 无法解析 intent — ${appId} 没有匹配的 intentFilter`
+ `且 intent 未提供 route (action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'})`,
);
return false;
}
if (!targetFilter) {
console.warn(
`[OS] startActivityForResult: ${appId} has no intentFilter for action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'}, using intent.route fallback`,
);
}
const activeTask = getActiveTask(deps.getState());
if (!activeTask) {
console.error('[OS] startActivityForResult: No active task');
return false;
}
const callerActivity = getTaskTopActivity(activeTask);
if (!callerActivity) {
console.error('[OS] startActivityForResult: No caller activity');
return false;
}
const requestCode = deps.allocRequestCode(callerActivity.activityId, callback);
// 调用方 intent.route 优先于 manifest filter.route。
// 真机对应:filter 决定哪个 Activity 接收,调用方通过 intent extras / data 决定 Activity 内部细节;
// 我们把 route 当成"caller hint"暴露出来,让调用方一次性指明目标子页(如 Settings → FM /category/images),
// 避免 OS 先 navigate('/') 又被 App-side dispatcher replace 到子页的双跳竞态。
// 缺省(未指定 intent.route)时仍走 filter.route。
const baseRoute = intent.route ?? targetFilter?.route ?? '/';
const newActivity: ActivityInstance = {
activityId: deps.nextActivityId(),
appId,
initialRoute: baseRoute,
intent,
requestCode,
callerActivityId: callerActivity.activityId,
};
console.log(
`[OSDBG] startActivityForResult caller=${callerAppId} target=${appId} route=${baseRoute} `
+ `action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'} `
+ `stack=${buildIntentDebugStack('startActivityForResult')}`,
);
console.log(`[OS] startActivityForResult: ${callerAppId}${appId} route=${baseRoute}`);
deps.pushActivity(activeTask.taskId, newActivity);
deps.navigateToActivity(newActivity.activityId, baseRoute, { fallbackAppId: newActivity.appId });
return true;
}
function launchResolvedIntent(
appId: AppId,
callerAppId: AppId,
intent: IntentPayload,
newTask: boolean,
deps: StartActivityPlainDeps,
): boolean {
if (!PackageManagerService.isInstalled(appId)) {
console.error(`[OS] startActivity: Invalid appId: ${appId}`);
return false;
}
ensureCallerQueryVisible(callerAppId, intent);
const targetFilter = pickTargetFilter(appId, intent);
if (!targetFilter && !intent.route) {
console.error(
`[OS] startActivity: 无法解析 intent — ${appId} 没有匹配的 intentFilter`
+ `且 intent 未提供 route (action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'})`,
);
return false;
}
// 同 forResult 路径:intent.route 优先(caller hint 一次性指明目标子页),缺省走 filter.route。
const baseRoute = intent.route ?? targetFilter?.route ?? '/';
const enrichedIntent: IntentPayload = {
...intent,
data: { ...intent.data, __callerAppId: callerAppId },
};
// 接收方声明 launchMode='singleTask' 时,强制 promote 到 newTask 路径,对应真机
// "Activity 上的 launchMode 由接收方决定,凌驾于调用方 flag" 的语义:
// 即使调用方没传 FLAG_ACTIVITY_NEW_TASKsingleTask Activity 也始终落在自己的 task 里。
// 典型例子:12306 不传 newTask 调 startActivity(ACTION_VIEW + scheme=sms)SMS 仍然进入自己的独立 task。
const launchMode = targetFilter?.launchMode ?? 'standard';
const effectiveNewTask = newTask || launchMode === 'singleTask';
console.log(
`[OSDBG] startActivity caller=${callerAppId} target=${appId} route=${baseRoute} newTask=${String(newTask)} `
+ `effectiveNewTask=${String(effectiveNewTask)} launchMode=${launchMode} `
+ `action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'} `
+ `stack=${buildIntentDebugStack('startActivity')}`,
);
if (effectiveNewTask) {
const callerTaskId = getActiveTask(deps.getState())?.taskId;
const taskExisted = deps.getState().tasks.some((t) => t.rootAppId === appId);
// singleTask + 已存在 Task:清栈到 root + 重置 root 历史 + 重新投递 intent。
// 对应真机 Activity 上 launchMode="singleTask" 的语义。
if (launchMode === 'singleTask' && taskExisted) {
const existingTask = deps.getState().tasks.find((t) => t.rootAppId === appId);
if (!existingTask || existingTask.stack.length === 0) return false;
// Pop 掉 root 之上所有 Activity(含 foreign-task 借栈来的)。
while (true) {
const cur = deps.getState().tasks.find((t) => t.rootAppId === appId);
if (!cur || cur.stack.length <= 1) break;
deps.popActivity(cur.taskId);
}
const refreshedTask = deps.getState().tasks.find((t) => t.rootAppId === appId);
if (!refreshedTask || refreshedTask.stack.length === 0) return false;
const rootActivity = refreshedTask.stack[0];
// 把 enrichedIntent 投递到 root activityShareForwardPage 通过 getIntentPayload 读取)。
deps.setActivityIntent(refreshedTask.taskId, rootActivity.activityId, enrichedIntent);
// 把 wechat Task 切到前台。LAUNCH_APP 不会动其它 task — Gallery Task 仍保留在后台。
deps.launchApp(appId);
// 重置 MemoryRouter 历史到 ['/', baseRoute]:先 popToRoot 退回 '/',再 push baseRoute。
requestAnimationFrame(() => {
// rAF 间隙里 task 可能被外部 closeTask 移除(如用户立刻在 recents 里划掉)。
// 此时 navigator 可能尚未走完 React 异步 unregister,仍能拿到引用,但不该再操作已关闭的 Task。
const stillExists = deps.getState().tasks.some((t) => t.rootAppId === appId);
if (!stillExists) {
console.warn(`[OS] startActivity(singleTask): ${appId} task closed before rAF — aborting nav reset`);
return;
}
const nav = AppNavigatorRegistry.get(appId);
if (!nav) {
console.warn(`[OS] startActivity(singleTask): navigator not registered for ${appId}`);
return;
}
if (nav.popToRoot) {
nav.popToRoot();
} else {
// Older AppNavigator without popToRoot — fallback to replace
nav.navigate('/', { replace: true });
}
nav.navigate(baseRoute, { replace: false });
});
console.log(`[OS] startActivity: ${callerAppId}${appId} route=${baseRoute} (singleTask, reused)`);
return true;
}
deps.launchApp(appId);
if (!taskExisted) {
deps.markExternalRoute(appId);
}
// Zustand dispatch is synchronous — locate the target activity immediately.
// Reverse-search for the activity belonging to appId (the stack top may be
// another app's activity pushed via same-task mode).
const postState = deps.getState();
const task = postState.tasks.find((t) => t.rootAppId === appId);
if (!task || task.stack.length === 0) return false;
if (!taskExisted) {
const targetActivity =
[...task.stack].reverse().find((act) => act.appId === appId)
?? task.stack[task.stack.length - 1];
deps.setActivityIntent(task.taskId, targetActivity.activityId, enrichedIntent);
const targetActivityId = targetActivity.activityId;
// singleTask 新建 Task 时用 push 模式,使根 Activity 历史为 ['/', baseRoute]
// 完成后 replace 到结果路由形成 ['/', resultRoute],返回键能回到主页 '/'.
// 默认 standard 走 replace(历史为 [baseRoute]),保持 ACTION_PAY 等"finish 即回调用方"的精确语义。
const replaceMode = launchMode !== 'singleTask';
requestAnimationFrame(() => {
deps.navigateToActivity(targetActivityId, baseRoute, { replace: replaceMode, fallbackAppId: appId });
});
} else {
const newActivity: ActivityInstance = {
activityId: deps.nextActivityId(),
appId,
initialRoute: baseRoute,
launchedByTaskId: callerTaskId,
intent: enrichedIntent,
};
deps.pushActivity(task.taskId, newActivity);
requestAnimationFrame(() => {
deps.navigateToActivity(newActivity.activityId, baseRoute, { fallbackAppId: appId });
});
}
console.log(`[OS] startActivity: ${callerAppId}${appId} route=${baseRoute} (newTask)`);
return true;
}
// Same-task mode: push new Activity into caller's Task
const activeTask = getActiveTask(deps.getState());
if (!activeTask) {
console.error('[OS] startActivity: No active task');
return false;
}
const newActivity: ActivityInstance = {
activityId: deps.nextActivityId(),
appId,
initialRoute: baseRoute,
intent: enrichedIntent,
};
console.log(`[OS] startActivity: ${callerAppId}${appId} route=${baseRoute} (sameTask)`);
deps.pushActivity(activeTask.taskId, newActivity);
deps.navigateToActivity(newActivity.activityId, baseRoute, { fallbackAppId: newActivity.appId });
return true;
}
export const IntentResolver = {
getState(): IntentChooserState {
return chooserState;
},
subscribe(listener: ChooserListener): () => void {
chooserListeners.add(listener);
listener(chooserState);
return () => chooserListeners.delete(listener);
},
chooseIntentActivity(appId: AppId): void {
const resolver = chooserResolver;
chooserResolver = null;
setChooser({ open: false, intent: null, matches: [] });
if (resolver) resolver(appId);
},
cancelIntentChooser(): void {
const resolver = chooserResolver;
chooserResolver = null;
setChooser({ open: false, intent: null, matches: [] });
if (resolver) resolver(null);
},
startActivityForResult(
appIdOrIntent: AppId | string | IntentPayload,
intentOrCallback: IntentPayload | ((result: ActivityResult) => void) | undefined,
callbackOrUndefined: ((result: ActivityResult) => void) | undefined,
deps: StartActivityDeps,
): boolean {
let appId: AppId | null = null;
let intent: IntentPayload;
let callback: (result: ActivityResult) => void;
if (typeof appIdOrIntent === 'string') {
appId = appIdOrIntent as AppId;
intent = intentOrCallback as IntentPayload;
callback = callbackOrUndefined!;
} else if (typeof appIdOrIntent === 'object' && appIdOrIntent && 'action' in appIdOrIntent) {
intent = appIdOrIntent;
callback = intentOrCallback as (result: ActivityResult) => void;
const matches = PackageManagerService.queryIntentActivities(intent);
if (matches.length === 0) {
console.error(
`[OS] startActivityForResult: 隐式解析无匹配 — action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'}`
);
return false;
}
if (matches.length > 1 && SIMULATOR_CONFIG.intent.chooserEnabled) {
if (chooserResolver) {
return false;
}
const activeTask = getActiveTask(deps.getState());
const top = getTaskTopActivity(activeTask);
const callerAppId = top?.appId ?? null;
if (!callerAppId) {
console.error('[OS] startActivityForResult: No active app');
return false;
}
void requestIntentChoice(intent, matches).then((chosen) => {
if (!chosen) {
requestAnimationFrame(() => callback({ resultCode: 'CANCELED' }));
return;
}
launchResolvedIntentForResult(chosen, callerAppId, intent, callback, deps);
});
return true;
}
appId = matches[0].appId;
console.log(`[OS] startActivityForResult: 隐式解析 → ${appId} (共 ${matches.length} 个匹配)`);
} else {
console.error('[OS] startActivityForResult: 参数格式错误');
return false;
}
if (typeof callback !== 'function') {
console.error('[OS] startActivityForResult: callback is required');
return false;
}
if (!appId) {
console.error('[OS] startActivityForResult: Invalid appId');
return false;
}
const activeTask = getActiveTask(deps.getState());
if (!activeTask) {
console.error('[OS] startActivityForResult: No active task');
return false;
}
const callerActivity = getTaskTopActivity(activeTask);
if (!callerActivity) {
console.error('[OS] startActivityForResult: No caller activity');
return false;
}
const callerAppId = callerActivity.appId;
return launchResolvedIntentForResult(appId, callerAppId, intent, callback, deps);
},
startActivity(
appIdOrIntent: AppId | string | IntentPayload,
intentOrOptions: IntentPayload | { newTask?: boolean } | undefined,
optionsOrUndefined: { newTask?: boolean } | undefined,
deps: StartActivityPlainDeps,
): boolean {
let appId: AppId | null = null;
let intent: IntentPayload;
let newTask = false;
if (typeof appIdOrIntent === 'string') {
appId = appIdOrIntent as AppId;
intent = intentOrOptions as IntentPayload;
newTask = !!optionsOrUndefined?.newTask;
} else if (typeof appIdOrIntent === 'object' && appIdOrIntent && 'action' in appIdOrIntent) {
intent = appIdOrIntent;
const opts = intentOrOptions as { newTask?: boolean } | undefined;
newTask = !!opts?.newTask;
const matches = PackageManagerService.queryIntentActivities(intent);
if (matches.length === 0) {
console.error(
`[OS] startActivity: 隐式解析无匹配 — action=${intent.action} scheme=${intent.scheme ?? '-'} type=${intent.type ?? '-'}`
);
return false;
}
if (matches.length > 1 && SIMULATOR_CONFIG.intent.chooserEnabled) {
if (chooserResolver) return false;
const activeTask = getActiveTask(deps.getState());
const top = getTaskTopActivity(activeTask);
const callerAppId = top?.appId ?? null;
if (!callerAppId) {
console.error('[OS] startActivity: No active app');
return false;
}
void requestIntentChoice(intent, matches).then((chosen) => {
if (!chosen) return;
launchResolvedIntent(chosen, callerAppId, intent, newTask, deps);
});
return true;
}
appId = matches[0].appId;
console.log(`[OS] startActivity: 隐式解析 → ${appId} (共 ${matches.length} 个匹配)`);
} else {
console.error('[OS] startActivity: 参数格式错误');
return false;
}
if (!appId) {
console.error('[OS] startActivity: Invalid appId');
return false;
}
const activeTask = getActiveTask(deps.getState());
if (!activeTask) {
console.error('[OS] startActivity: No active task');
return false;
}
const callerActivity = getTaskTopActivity(activeTask);
if (!callerActivity) {
console.error('[OS] startActivity: No caller activity');
return false;
}
return launchResolvedIntent(appId, callerActivity.appId, intent, newTask, deps);
},
};
export default IntentResolver;