feat(flow): on-demand host handoff with reliable return and a back-tap tip
Keyboard
- Only jump to the app when the voice surface is shown (tab tap / opened
there); typing tabs no longer leave the host on keyboard open.
- Wait up to 0.4 s for the arbiter to report the host app before leaving,
and late-bind an id that arrives within 6 s of the jump.
- Empty arbiter callbacks no longer wipe an already captured host id;
install the capture handler in every build configuration.
App
- Return to the previous app with up to four retries using iOS's real
open result, and honor the skip-app-switch preference.
- Keep the pending host id across cold-start zombie reconciliation, and
expire it after two minutes (legacy values without a timestamp stay valid).
- Show a light tip pointing at the system back control when we cannot
return automatically ("点击返回,立即使用").
Tests: TTL behaviour and return retry policy.
This commit is contained in:
@@ -176,9 +176,9 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
#if DEBUG
|
||||
HostBundleProbe.installLogging()
|
||||
#endif
|
||||
// Host identification (return-to-app after a cold-start handoff) runs in
|
||||
// every build; the handler also writes the DEBUG probe log.
|
||||
HostAppIdentity.installCaptureHandler()
|
||||
// Voice-first keyboard — hide the misleading "English" subtitle in Settings.
|
||||
primaryLanguage = "mis"
|
||||
let preferred = TypingInputConfiguration.preferredSurfaceOnOpen()
|
||||
@@ -373,10 +373,12 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
lockPresentedKeyboardHeight()
|
||||
refreshReturnKeyRole()
|
||||
scheduleTypingDocumentSynchronization()
|
||||
// Presentation math is locked before arming the PiP handoff.
|
||||
// Presentation math is locked before arming the PiP handoff. Only a
|
||||
// keyboard that opens straight onto the voice surface arms the host here;
|
||||
// typing opens never leave the host app.
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self, self.heightPhase == .presented else { return }
|
||||
self.flowCoordinator.ensurePiPReadyOnKeyboardOpen()
|
||||
self.flowCoordinator.armHostForVoiceSurfaceIfNeeded(reason: "keyboardOpened")
|
||||
}
|
||||
#if DEBUG
|
||||
HostBundleProbe.recordPresentation()
|
||||
@@ -798,7 +800,13 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
state.copySelection = { [weak self] in self?.textInserter.copySelection() }
|
||||
state.cutSelection = { [weak self] in self?.textInserter.cutSelection() }
|
||||
state.setSurface = { [weak self] surface in
|
||||
self?.applySurface(surface)
|
||||
guard let self else { return }
|
||||
self.applySurface(surface)
|
||||
// Landing on the voice surface (tab tap, or an auto-fired clipboard
|
||||
// skill taking over the assistant surface) is the demand signal for
|
||||
// the host app. The coordinator ignores any other surface, and its
|
||||
// cooldown absorbs repeated flips.
|
||||
self.flowCoordinator.armHostForVoiceSurfaceIfNeeded(reason: "surfaceSelected")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user