Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,204 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "dictation_session.h"
#include "dictation_session_private.h"
#include "voice_window_private.h"
#include "applib/voice/voice_window.h"
#include "applib/applib_malloc.auto.h"
#include "process_management/app_install_manager.h"
#include "syscall/syscall.h"
#include "system/logging.h"
#include "system/passert.h"
#include <string.h>
#if CAPABILITY_HAS_MICROPHONE
static void prv_handle_transcription_result(PebbleEvent *e, void *context) {
PBL_ASSERTN(context);
PBL_LOG(LOG_LEVEL_DEBUG, "Exiting with status code: %"PRId8, e->dictation.result);
DictationSession *session = context;
session->callback(session, e->dictation.result, e->dictation.text, session->context);
voice_window_reset(session->voice_window);
session->in_progress = false;
if (session->destroy_pending) {
dictation_session_destroy(session);
}
}
static void prv_app_focus_handler(PebbleEvent *e, void *context) {
DictationSession *session = context;
if (e->app_focus.in_focus) {
event_service_client_subscribe(&session->dictation_result_sub);
voice_window_regain_focus(session->voice_window);
} else {
event_service_client_unsubscribe(&session->dictation_result_sub);
voice_window_lose_focus(session->voice_window);
}
}
static void prv_stop_session(DictationSession *session) {
session->in_progress = false;
event_service_client_unsubscribe(&session->dictation_result_sub);
if (pebble_task_get_current() == PebbleTask_App) {
event_service_client_unsubscribe(&session->app_focus_sub);
}
}
#endif
DictationSession *dictation_session_create(uint32_t buffer_size,
DictationSessionStatusCallback callback, void *context) {
#if CAPABILITY_HAS_MICROPHONE
if (!callback) {
return NULL;
}
// Old versions of the Android app (<3.5) will allow voice replies (which also use this code-path)
// but don't set the capability flag, so we don't want to block all requests here, just those from
// apps. This will result in apps not being able to use the voice APIs unless the phone has the
// capability flag set, which is what we want.
bool from_app = (pebble_task_get_current() == PebbleTask_App) &&
!app_install_id_from_system(sys_process_manager_get_current_process_id());
if (from_app && !sys_system_pp_has_capability(CommSessionVoiceApiSupport)) {
PBL_LOG(LOG_LEVEL_INFO, "No phone connected or phone app does not support app-initiated "
"dictation sessions");
return NULL;
}
DictationSession *session = applib_type_malloc(DictationSession);
if (!session) {
return NULL;
}
char *buffer = NULL;
if (buffer_size > 0) {
buffer = applib_malloc(buffer_size);
if (!buffer) {
applib_free(session);
return NULL;
}
}
VoiceWindow *voice_window = voice_window_create(buffer, buffer_size,
VoiceEndpointSessionTypeDictation);
if (!voice_window) {
applib_free(buffer);
applib_free(session);
return NULL;
}
*session = (DictationSession) {
.callback = callback,
.context = context,
.voice_window = voice_window,
.dictation_result_sub = (EventServiceInfo) {
.type = PEBBLE_DICTATION_EVENT,
.handler = prv_handle_transcription_result,
.context = session
}
};
if (pebble_task_get_current() == PebbleTask_App) {
session->app_focus_sub = (EventServiceInfo) {
.type = PEBBLE_APP_DID_CHANGE_FOCUS_EVENT,
.handler = prv_app_focus_handler,
.context = session
};
}
#else
DictationSession *session = NULL;
#endif
return session;
}
void dictation_session_destroy(DictationSession *session) {
#if CAPABILITY_HAS_MICROPHONE
if (!session) {
return;
}
if (session->in_progress) {
// we can't destroy a session while it is in progress,
// so we mark it as destroy pending and we'll destroy it later
session->destroy_pending = true;
return;
}
prv_stop_session(session);
voice_window_destroy(session->voice_window);
applib_free(session);
#endif
}
void dictation_session_enable_confirmation(DictationSession *session, bool is_enabled) {
#if CAPABILITY_HAS_MICROPHONE
if (!session || session->in_progress) {
return;
}
voice_window_set_confirmation_enabled(session->voice_window, is_enabled);
#endif
}
void dictation_session_enable_error_dialogs(DictationSession *session, bool is_enabled) {
#if CAPABILITY_HAS_MICROPHONE
if (!session || session->in_progress) {
return;
}
voice_window_set_error_enabled(session->voice_window, is_enabled);
#endif
}
DictationSessionStatus dictation_session_start(DictationSession *session) {
#if CAPABILITY_HAS_MICROPHONE
if (!session || session->in_progress) {
return DictationSessionStatusFailureInternalError;
}
DictationSessionStatus result = voice_window_push(session->voice_window);
if (result != DictationSessionStatusSuccess) {
return result;
}
session->in_progress = true;
event_service_client_subscribe(&session->dictation_result_sub);
if (pebble_task_get_current() == PebbleTask_App) {
event_service_client_subscribe(&session->app_focus_sub);
}
return result;
#else
return DictationSessionStatusFailureInternalError;
#endif
}
DictationSessionStatus dictation_session_stop(DictationSession *session) {
#if CAPABILITY_HAS_MICROPHONE
if (!session || !session->in_progress) {
return DictationSessionStatusFailureInternalError;
}
prv_stop_session(session);
voice_window_pop(session->voice_window);
return DictationSessionStatusSuccess;
#else
return DictationSessionStatusFailureInternalError;
#endif
}

View file

@ -0,0 +1,150 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
//! @file voice/dictation_session.h
//! Defines the interface to the dictation session API
//! @addtogroup Microphone
//! @{
//! @addtogroup DictationSession Dictation Session
//! \brief A dictation session allows the retrieval of a voice transcription from the Pebble
//! smartwatch's speech recognition provider via the same user interface used by the Pebble OS for
//! notifications.
//!
//! Starting a session will spawn the UI and upon user confirmation (unless this is disabled), the
//! result of the session as well as the transcription text will be returned via callback. If user
//! confirmation is disabled the first transcription result will be passed back via the callback.
//!
//! A dictation session must be created before use (see \ref dictation_session_create) and can
//! be reused for however many dictations are required, using \ref dictation_session_start. A
//! session can be aborted mid-flow by calling \ref dictation_session_stop.
//!
//! If these calls are made on a platform that does not support voice dictation,
//! \ref dictation_session_create will return NULL and the other calls will do nothing.
typedef struct DictationSession DictationSession;
// convenient macros to distinguish between mic and no mic.
// TODO: PBL-21978 remove redundant comments as a workaround around for SDK generator
#if defined(PBL_MICROPHONE)
//! Convenience macro to switch between two expressions depending on mic support.
//! On platforms with a mic the first expression will be chosen, the second otherwise.
#define PBL_IF_MICROPHONE_ELSE(if_true, if_false) (if_true)
#else
//! Convenience macro to switch between two expressions depending on mic support.
//! On platforms with a mic the first expression will be chosen, the second otherwise.
#define PBL_IF_MICROPHONE_ELSE(if_true, if_false) (if_false)
#endif
typedef enum {
//! Transcription successful, with a valid result
DictationSessionStatusSuccess,
//! User rejected transcription and exited UI
DictationSessionStatusFailureTranscriptionRejected,
//! User exited UI after transcription error
DictationSessionStatusFailureTranscriptionRejectedWithError,
//! Too many errors occurred during transcription and the UI exited
DictationSessionStatusFailureSystemAborted,
//! No speech was detected and UI exited
DictationSessionStatusFailureNoSpeechDetected,
//! No BT or internet connection
DictationSessionStatusFailureConnectivityError,
//! Voice transcription disabled for this user
DictationSessionStatusFailureDisabled,
//! Voice transcription failed due to internal error
DictationSessionStatusFailureInternalError,
//! Cloud recognizer failed to transcribe speech (only possible if error dialogs disabled)
DictationSessionStatusFailureRecognizerError,
} DictationSessionStatus;
//! Dictation status callback. Indicates success or failure of the dictation session and, if
//! successful, passes the transcribed string to the user of the dictation session. The transcribed
//! string will be freed after this call returns, so the string should be copied if it needs to be
//! retained afterwards.
//! @param session dictation session from which the status was received
//! @param status dictation status
//! @param transcription transcribed string
//! @param context callback context specified when starting the session
typedef void (*DictationSessionStatusCallback)(DictationSession *session,
DictationSessionStatus status, char *transcription,
void *context);
//! Create a dictation session. The session object can be used more than once to get a
//! transcription. When a transcription is received a buffer will be allocated to store the text in
//! with a maximum size specified by \ref buffer_size. When a transcription and accepted by the user
//! or a failure of some sort occurs, the callback specified will be called with the status and the
//! transcription if one was accepted.
//! @param buffer_size size of buffer to allocate for the transcription text; text will be
//! truncated if it is longer than the maximum size specified; a size of 0
//! will allow the session to allocate as much as it needs and text will
//! not be truncated
//! @param callback dictation session status handler (must be valid)
//! @param callback_context context pointer for status handler
//! @return handle to the dictation session or NULL if the phone app is not connected or does not
//! support voice dictation, if this is called on a platform that doesn't support voice dictation,
//! or if an internal error occurs.
DictationSession *dictation_session_create(uint32_t buffer_size,
DictationSessionStatusCallback callback,
void *callback_context);
//! Destroy the dictation session and free its memory. Will terminate a session in progress.
//! @param session dictation session to be destroyed
void dictation_session_destroy(DictationSession *session);
//! Enable or disable user confirmation of transcribed text, which allows the user to accept or
//! reject (and restart) the transcription. Must be called before the session is started.
//! @param session dictation session to modify
//! @param is_enabled set to true to enable user confirmation of transcriptions (default), false
//! to disable
void dictation_session_enable_confirmation(DictationSession *session, bool is_enabled);
//! Enable or disable error dialogs when transcription fails. Must be called before the session
//! is started. Disabling error dialogs will also disable automatic retries if transcription fails.
//! @param session dictation session to modify
//! @param is_enabled set to true to enable error dialogs (default), false to disable
void dictation_session_enable_error_dialogs(DictationSession *session, bool is_enabled);
//! Start the dictation session. The dictation UI will be shown. When the user accepts a
//! transcription or exits the UI, or, when the confirmation dialog is disabled and a status is
//! received, the status callback will be called. Can only be called when no session is in progress.
//! The session can be restarted multiple times after the UI is exited or the session is stopped.
//! @param session dictation session to start or restart
//! @return true if session was started, false if session is already in progress or is invalid.
DictationSessionStatus dictation_session_start(DictationSession *session);
//! Stop the current dictation session. The UI will be hidden and no status callbacks will be
//! received after the session is stopped.
//! @param session dictation session to stop
//! @return true if session was stopped, false if session was not started or is invalid
DictationSessionStatus dictation_session_stop(DictationSession *session);
//! @} // end addtogroup DictationSession
//! @} // end addtogroup Microphone

View file

@ -0,0 +1,34 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "voice_window.h"
#include "dictation_session.h"
#include "applib/event_service_client.h"
#include <stdint.h>
#include <stdbool.h>
struct DictationSession {
VoiceWindow *voice_window;
DictationSessionStatusCallback callback;
void *context;
bool in_progress;
bool destroy_pending;
EventServiceInfo dictation_result_sub;
EventServiceInfo app_focus_sub;
};

View file

@ -0,0 +1,100 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "loading_layer.h"
#include "applib/graphics/gtypes.h"
#include "applib/ui/property_animation.h"
#include <string.h>
void loading_layer_init(LoadingLayer *loading_layer, const GRect *frame) {
*loading_layer = (LoadingLayer) {
.full_frame = *frame
};
ProgressLayer *progress_layer = &loading_layer->progress_layer;
progress_layer_init(progress_layer, frame);
progress_layer_set_corner_radius(progress_layer, PROGRESS_SUGGESTED_CORNER_RADIUS);
}
void loading_layer_deinit(LoadingLayer *loading_layer) {
loading_layer_pause(loading_layer);
progress_layer_deinit(&loading_layer->progress_layer);
}
void loading_layer_shrink(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration,
AnimationStoppedHandler stopped_handler, void *context) {
loading_layer_pause(loading_layer);
layer_set_frame((Layer *)loading_layer, &loading_layer->full_frame);
GRect *start = &loading_layer->full_frame;
GRect stop = *start;
stop.origin.x += stop.size.w;
stop.size.w = 0;
PropertyAnimation *prop_anim = property_animation_create_layer_frame(
(Layer *)loading_layer, start, &stop);
if (!prop_anim) {
return;
}
Animation *animation = property_animation_get_animation(prop_anim);
// If we failed, pause on the screen for a little.
animation_set_delay(animation, delay);
animation_set_duration(animation, duration);
animation_set_curve(animation, AnimationCurveEaseOut);
animation_set_handlers(animation, (AnimationHandlers) {
.stopped = stopped_handler
}, context);
loading_layer->animation = animation;
animation_schedule(animation);
}
void loading_layer_pause(LoadingLayer *loading_layer) {
if (animation_is_scheduled(loading_layer->animation)) {
animation_unschedule(loading_layer->animation);
}
}
void loading_layer_grow(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration) {
loading_layer_pause(loading_layer);
if (duration == 0) {
layer_set_frame((Layer *)loading_layer, &loading_layer->full_frame);
return;
}
GRect start = loading_layer->full_frame;
start.size.w = 0;
layer_set_frame((Layer *)loading_layer, &start);
PropertyAnimation *prop_anim = property_animation_create_layer_frame(
(Layer *)loading_layer, &start, &loading_layer->full_frame);
if (!prop_anim) {
return;
}
Animation *animation = property_animation_get_animation(prop_anim);
animation_set_delay(animation, delay);
animation_set_duration(animation, duration);
animation_set_curve(animation, AnimationCurveEaseOut);
loading_layer->animation = animation;
animation_schedule(animation);
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "applib/ui/animation.h"
#include "applib/ui/progress_layer.h"
#include <stdint.h>
#include <stdbool.h>
#define LOADING_LAYER_DEFAULT_SIZE { 79, PROGRESS_SUGGESTED_HEIGHT }
typedef void (*LoadingLayerAnimCompleteCb)(void *context);
typedef struct {
ProgressLayer progress_layer;
Animation *animation;
GRect full_frame;
} LoadingLayer;
void loading_layer_init(LoadingLayer *loading_layer, const GRect *frame);
void loading_layer_deinit(LoadingLayer *loading_layer);
void loading_layer_shrink(LoadingLayer *loading_layer, uint32_t delay, uint32_t duration,
AnimationStoppedHandler stopped_handler, void *context);
void loading_layer_grow(LoadingLayer *loading_layer, uint32_t duration, uint32_t delay);
void loading_layer_pause(LoadingLayer *loading_layer);

View file

@ -0,0 +1,277 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "transcription_dialog.h"
#include "applib/applib_malloc.auto.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/text_render.h"
#include "applib/graphics/utf8.h"
#include "applib/ui/action_bar_layer.h"
#include "applib/ui/animation.h"
#include "applib/ui/animation_interpolate.h"
#include "applib/ui/dialogs/dialog_private.h"
#include "applib/ui/scroll_layer.h"
#include "kernel/ui/kernel_ui.h"
#include "resource/resource_ids.auto.h"
#include "system/passert.h"
#include <stdlib.h>
#include <string.h>
#define SCROLL_ANIMATION_DURATION (300)
#define POP_WINDOW_DELAY (400)
#define CHARACTER_DELAY (20)
#define TEXT_OFFSET_VERTICAL (6)
static void prv_show_next_character(TranscriptionDialog *transcription_dialog, int16_t to_idx) {
// TODO: at the beginning of a word, check whether it's going to wrap when it's finished
// type and break to the next line before it starts typing.
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
// Find the current index
utf8_t *cursor = (utf8_t *)dialog->buffer;
int16_t current_idx = 0;
while (cursor < (utf8_t *)transcription_dialog->zero) {
cursor = utf8_get_next(cursor);
current_idx++;
}
PBL_ASSERTN(cursor == (utf8_t *)transcription_dialog->zero);
PBL_ASSERTN(current_idx <= to_idx);
// Restore the missing character, then get the start of the next codepoint.
*transcription_dialog->zero = transcription_dialog->missing;
while (current_idx++ < to_idx) {
cursor = utf8_get_next(cursor);
}
char *next = (char *)cursor;
if (next == dialog->buffer + transcription_dialog->buffer_len) {
return;
}
// Move the zero terminator.
transcription_dialog->missing = *next;
*next = '\0';
transcription_dialog->zero = next;
}
static void prv_set_char_index(void *subject, int16_t index) {
TranscriptionDialog *transcription_dialog = subject;
prv_show_next_character(transcription_dialog, index);
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
ScrollLayer *scroll_layer = &((ExpandableDialog *) transcription_dialog)->scroll_layer;
TextLayer *text_layer = &dialog->text_layer;
const GSize size = text_layer_get_content_size(graphics_context_get_current_context(),
text_layer);
const uint16_t font_height = fonts_get_font_height(text_layer->font);
text_layer_set_size(text_layer, (GSize) { text_layer->layer.frame.size.w, size.h + font_height });
const GSize scroll_size = scroll_layer_get_content_size(scroll_layer);
const int16_t new_height = size.h + TEXT_OFFSET_VERTICAL;
if (scroll_size.h != new_height) {
const GRect *bounds = &scroll_layer_get_layer(scroll_layer)->bounds;
GPoint offset = { .y = bounds->size.h - new_height };
#if PBL_ROUND
// do paging on round display
offset.y = ROUND_TO_MOD_CEIL(offset.y, scroll_layer->layer.frame.size.h);
#endif
scroll_layer_set_content_size(scroll_layer,
(GSize) { scroll_layer->layer.frame.size.w, new_height });
scroll_layer_set_content_offset(scroll_layer, offset, true /* animated */);
animation_set_duration(property_animation_get_animation(scroll_layer->animation),
SCROLL_ANIMATION_DURATION);
}
layer_mark_dirty((Layer *)text_layer);
}
static void prv_start_text_animation(TranscriptionDialog *transcription_dialog) {
static const PropertyAnimationImplementation animated_text_len = {
.base = {
.update = (AnimationUpdateImplementation) property_animation_update_int16,
},
.accessors = {
.setter = { .int16 = prv_set_char_index }
}
};
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
// Count the number of codepoints in the message
*transcription_dialog->zero = transcription_dialog->missing;
int16_t count = 0;
int16_t begin = 0;
utf8_t *cursor = (utf8_t *)dialog->buffer;
while ((cursor = utf8_get_next(cursor))) {
count++;
if (cursor == (utf8_t *)transcription_dialog->zero) {
begin = count;
}
}
transcription_dialog->animation = property_animation_create(&animated_text_len,
transcription_dialog, NULL, NULL);
if (!transcription_dialog->animation) {
return;
}
property_animation_set_from_int16(transcription_dialog->animation, &begin);
property_animation_set_to_int16(transcription_dialog->animation, &count);
Animation *anim = property_animation_get_animation(transcription_dialog->animation);
animation_set_duration(anim, (count - begin) * CHARACTER_DELAY);
animation_set_curve(anim, AnimationCurveEaseInOut);
// Text is shown if creating the property animation fails
*transcription_dialog->zero = '\0';
animation_set_curve(anim, AnimationCurveLinear);
animation_schedule(anim);
}
static void prv_stop_text_animation(TranscriptionDialog *transcription_dialog) {
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
animation_unschedule(property_animation_get_animation(transcription_dialog->animation));
*transcription_dialog->zero = transcription_dialog->missing;
transcription_dialog->zero = dialog->buffer + transcription_dialog->buffer_len;
transcription_dialog->missing = '\0';
layer_mark_dirty((Layer *)&dialog->text_layer);
}
static void prv_transcription_dialog_unload(void *context) {
TranscriptionDialog *transcription_dialog = context;
app_timer_cancel(transcription_dialog->pop_timer);
prv_stop_text_animation(transcription_dialog);
}
static void prv_transcription_dialog_load(void *context) {
TranscriptionDialog *transcription_dialog = context;
transcription_dialog->was_pushed = true;
if (transcription_dialog->buffer_len > 0) {
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
transcription_dialog->zero = dialog->buffer;
transcription_dialog->missing = dialog->buffer[0];
prv_start_text_animation(context);
}
}
static void prv_transcription_dialog_select_cb(void *context) {
TranscriptionDialog *transcription_dialog = context;
if (transcription_dialog->keep_alive_on_select) {
action_bar_layer_clear_icon(&transcription_dialog->e_dialog.action_bar, BUTTON_ID_SELECT);
} else {
transcription_dialog_pop(transcription_dialog);
}
}
static void prv_transcription_dialog_select_handler(ClickRecognizerRef recognizer, void *context) {
TranscriptionDialog *transcription_dialog = context;
if (transcription_dialog->select_pressed) {
// We are waiting to pop the window, don't run the callback again
return;
}
transcription_dialog->select_pressed = true;
prv_stop_text_animation(transcription_dialog);
if (transcription_dialog->callback) {
if (transcription_dialog->callback_context) {
transcription_dialog->callback(transcription_dialog->callback_context);
} else {
transcription_dialog->callback(transcription_dialog);
}
}
transcription_dialog->pop_timer = app_timer_register(POP_WINDOW_DELAY,
prv_transcription_dialog_select_cb, transcription_dialog);
}
void transcription_dialog_update_text(TranscriptionDialog *transcription_dialog,
char *buffer, uint16_t buffer_len) {
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
transcription_dialog->buffer_len = buffer_len;
dialog_set_text_buffer(dialog, buffer, false /* take_ownership */);
if (transcription_dialog->was_pushed) {
prv_stop_text_animation(transcription_dialog);
prv_start_text_animation(transcription_dialog);
}
}
void transcription_dialog_push(TranscriptionDialog *transcription_dialog,
WindowStack *window_stack) {
PBL_ASSERTN(transcription_dialog);
expandable_dialog_push((ExpandableDialog *)transcription_dialog, window_stack);
}
void app_transcription_dialog_push(TranscriptionDialog *transcription_dialog) {
PBL_ASSERTN(transcription_dialog);
app_expandable_dialog_push((ExpandableDialog *)transcription_dialog);
}
void transcription_dialog_pop(TranscriptionDialog *transcription_dialog) {
PBL_ASSERTN(transcription_dialog);
expandable_dialog_pop((ExpandableDialog *)transcription_dialog);
}
void transcription_dialog_set_callback(TranscriptionDialog *transcription_dialog,
TranscriptionConfirmationCallback callback,
void *callback_context) {
PBL_ASSERTN(transcription_dialog);
transcription_dialog->callback = callback;
transcription_dialog->callback_context = callback_context;
}
void transcription_dialog_keep_alive_on_select(TranscriptionDialog *transcription_dialog,
bool keep_alive_on_select) {
PBL_ASSERTN(transcription_dialog);
transcription_dialog->keep_alive_on_select = keep_alive_on_select;
}
TranscriptionDialog *transcription_dialog_create(void) {
TranscriptionDialog *transcription_dialog = applib_type_malloc(TranscriptionDialog);
if (!transcription_dialog) {
return NULL;
}
transcription_dialog_init(transcription_dialog);
return transcription_dialog;
}
void transcription_dialog_init(TranscriptionDialog *transcription_dialog) {
*transcription_dialog = (TranscriptionDialog){};
expandable_dialog_init((ExpandableDialog *)transcription_dialog, "Transcription Dialog");
expandable_dialog_set_select_action((ExpandableDialog *)transcription_dialog,
RESOURCE_ID_ACTION_BAR_ICON_CHECK, prv_transcription_dialog_select_handler);
Dialog *dialog = expandable_dialog_get_dialog((ExpandableDialog *)transcription_dialog);
dialog_set_callbacks(dialog, &(DialogCallbacks) {
.unload = prv_transcription_dialog_unload,
.load = prv_transcription_dialog_load
}, transcription_dialog);
dialog_show_status_bar_layer(dialog, true /* show status bar */);
dialog_set_timeout(dialog, DIALOG_TIMEOUT_INFINITE);
status_bar_layer_set_colors(&dialog->status_layer, GColorLightGray, GColorBlack);
}

View file

@ -0,0 +1,98 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "applib/app_timer.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/dialogs/expandable_dialog.h"
#include "applib/ui/window_stack.h"
#include <stdint.h>
#include <stdbool.h>
//! Callback from the dialog
typedef void(*TranscriptionConfirmationCallback)(void *callback_context);
typedef struct TranscriptionDialog {
ExpandableDialog e_dialog;
AppTimer *pop_timer;
TranscriptionConfirmationCallback callback;
void *callback_context;
char *zero;
char missing;
bool was_pushed;
bool select_pressed;
bool keep_alive_on_select;
PropertyAnimation *animation;
// We cache this value so that we don't have to recompute it
// in our animation helpers.
uint32_t buffer_len;
} TranscriptionDialog;
//! Creates a TranscriptionDialog on the heap.
//! @return \ref TranscriptionDialog
TranscriptionDialog *transcription_dialog_create(void);
//! Initialize a transcription dialog that was already allocated
void transcription_dialog_init(TranscriptionDialog *transcription_dialog);
//! Pushes a TranscriptionDialog onto the app window stack if running on
//! the app task, otherwise the modal window stack.
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to push
//! @param window_stack Pointer to the \ref WindowStack to push to
void transcription_dialog_push(TranscriptionDialog *transcription_dialog,
WindowStack *window_stack);
//! Pushes a \ref TranscriptionDialog to the app's window stack
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to push
//! @note: Put a better comment here before exporting
void app_transcription_dialog_push(TranscriptionDialog *transcription_dialog);
//! Pops a \ref TranscriptionDialog from the app window stack or modal window
//! stack depending on the current task.
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to pop
void transcription_dialog_pop(TranscriptionDialog *transcription_dialog);
//! Updates the text in a \ref TranscriptionDialog. This causes the dialog to
//! re-render and animate its contents.
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog text to set
//! @param transcription The text to display
//! @param transcription_len The length of the text in the transcription
void transcription_dialog_update_text(TranscriptionDialog *transcription_dialog,
char *transcription, uint16_t transcription_len);
//! Sets the callback that is called if the user confirms that the text
//! being displayed is what they intended.
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to set
//! @param callback The \ref TranscriptionConfirmationCallback to call if the user confirms text
//! @param callback_context The \ref callback_context to pass to the confirmation handler
//! @note If the callback_context is NULL, then the \ref TranscriptionDialog will be passed
//! the callback handler.
void transcription_dialog_set_callback(TranscriptionDialog *transcription_dialog,
TranscriptionConfirmationCallback callback,
void *callback_context);
//! @internal
//! Control whether the dialog closes when the select button is pressed
//! @param transcription_dialog Pointer to the \ref TranscriptionDialog to set
//! @param keep_alive_on_select If True the window will NOT close when select is pressed
//! @note The default is false (window will close when the selection has been made)
void transcription_dialog_keep_alive_on_select(TranscriptionDialog *transcription_dialog,
bool keep_alive_on_select);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/uuid.h"
#include "applib/voice/dictation_session.h"
#include "services/normal/voice_endpoint.h"
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct VoiceUiData VoiceWindow;
VoiceWindow *voice_window_create(char *buffer, size_t buffer_size,
VoiceEndpointSessionType session_type);
void voice_window_destroy(VoiceWindow *voice_window);
// Push the voice window from App task or Main task
DictationSessionStatus voice_window_push(VoiceWindow *voice_window);
void voice_window_pop(VoiceWindow *voice_window);
void voice_window_set_confirmation_enabled(VoiceWindow *voice_window, bool enabled);
void voice_window_set_error_enabled(VoiceWindow *voice_window, bool enabled);
void voice_window_reset(VoiceWindow *voice_window);

View file

@ -0,0 +1,110 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "loading_layer.h"
#include "transcription_dialog.h"
#include "applib/app_timer.h"
#include "applib/event_service_client.h"
#include "applib/ui/animation.h"
#include "applib/ui/layer.h"
#include "applib/ui/property_animation.h"
#include "applib/ui/text_layer.h"
#include "applib/ui/status_bar_layer.h"
#include "applib/ui/window.h"
#include "applib/ui/dialogs/bt_conn_dialog.h"
#include "applib/ui/dialogs/simple_dialog.h"
#include "applib/ui/dialogs/expandable_dialog.h"
#include "applib/ui/kino/kino_layer.h"
#include "applib/voice/dictation_session.h"
#include <stdint.h>
#include <stdbool.h>
typedef enum {
StateStart, // Start state. Nothing happens
StateStartWaitForReady, // Dot flies in
StateWaitForReady, // Progress bar shows and animates, dot pulses
StateStopWaitForReady, // Progress bar shrinks, dot continues to animate
StateRecording, // Microphone unfolds and text appears
StateStopRecording, // Microphone folds up again and text disappears
StateWaitForResponse, // Dot pulses, progress bar shown
StateStopWaitForResponse, // Progress bar shrinks
StateTransitionToText, // Dot flies out, text window pushed
StateError,
StateFinished,
StateExiting,
} VoiceUiState;
typedef struct VoiceUiData {
struct {
Window window;
KinoLayer icon_layer;
Animation *mic_dot_anim;
Layer mic_dot_layer;
int16_t mic_dot_radius;
TextLayer text_layer;
char text_buffer[20]; // Larger than needed because i18n
StatusBarLayer status_bar;
LoadingLayer progress_bar;
PropertyAnimation *progress_anim;
PropertyAnimation *fly_anim;
} mic_window;
union{
TranscriptionDialog transcription_dialog;
ExpandableDialog long_error_dialog;
SimpleDialog short_error_dialog;
BtConnDialog bt_dialog;
Dialog dialog;
};
VoiceUiState state;
bool speech_detected;
bool transcription_dialog_keep_alive_on_select;
char *message;
size_t message_len;
time_t timestamp;
uint8_t error_count;
bool last_session_successful;
uint8_t num_sessions;
AppTimer *dictation_timeout;
EventServiceInfo voice_event_sub;
DictationSessionStatus error_exit_status;
char error_text_buffer[150];
// For API access
size_t buffer_size;
bool show_confirmation_dialog;
bool show_error_dialog;
// Used to keep track of total elapsed time of transcriptions
uint64_t start_ms;
uint64_t elapsed_ms;
VoiceSessionId session_id;
VoiceEndpointSessionType session_type;
} VoiceUiData;
void voice_window_lose_focus(VoiceWindow *voice_window);
void voice_window_regain_focus(VoiceWindow *voice_window);
void voice_window_transcription_dialog_keep_alive_on_select(VoiceWindow *voice_window,
bool keep_alive_on_select);