Index: chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.cc |
diff --git a/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.cc b/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.cc |
index 4362d00abbd403e1e4e3b8ef35abe162534a6db7..0f11ecec3383a175c0a9f564ca54111d6b3dfc64 100644 |
--- a/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.cc |
+++ b/chrome/browser/chromeos/arc/voice_interaction/arc_voice_interaction_framework_service.cc |
@@ -11,10 +11,12 @@ |
#include "ash/public/cpp/shell_window_ids.h" |
#include "ash/shell.h" |
#include "base/bind.h" |
+#include "base/callback.h" |
#include "base/command_line.h" |
#include "base/containers/flat_set.h" |
#include "base/logging.h" |
#include "base/memory/ref_counted.h" |
+#include "base/metrics/histogram_macros.h" |
#include "base/metrics/user_metrics.h" |
#include "base/metrics/user_metrics_action.h" |
#include "base/task_scheduler/post_task.h" |
@@ -32,6 +34,7 @@ |
#include "ui/compositor/layer.h" |
#include "ui/compositor/layer_owner.h" |
#include "ui/compositor/layer_tree_owner.h" |
+#include "ui/gfx/geometry/rect.h" |
#include "ui/gfx/image/image.h" |
#include "ui/gfx/image/image_util.h" |
#include "ui/gfx/native_widget_types.h" |
@@ -46,6 +49,17 @@ namespace { |
using LayerSet = base::flat_set<const ui::Layer*>; |
+// Time out for a context query from container since user initiated |
+// interaction. This must be strictly less than |
+// kMaxTimeSinceUserInteractionForHistogram so that the histogram |
+// could cover the range of normal operations. |
+constexpr base::TimeDelta kAllowedTimeSinceUserInteraction = |
+ base::TimeDelta::FromSeconds(2); |
+constexpr base::TimeDelta kMaxTimeSinceUserInteractionForHistogram = |
+ base::TimeDelta::FromSeconds(5); |
+ |
+constexpr int32_t kContextRequestMaxRemainingCount = 2; |
+ |
void ScreenshotCallback( |
const mojom::VoiceInteractionFrameworkHost::CaptureFocusedWindowCallback& |
callback, |
@@ -185,13 +199,9 @@ bool ArcVoiceInteractionFrameworkService::AcceleratorPressed( |
// Temporary, used for debugging. |
// Does not take into account or update the palette state. |
- mojom::VoiceInteractionFrameworkInstance* framework_instance = |
- ARC_GET_INSTANCE_FOR_METHOD( |
- arc_bridge_service()->voice_interaction_framework(), |
- SetMetalayerVisibility); |
- DCHECK(framework_instance); |
- framework_instance->SetMetalayerVisibility(true); |
- |
+ // Explicitly call ShowMetalayer() to take into account user interaction |
+ // initiations. |
+ ShowMetalayer(base::Bind([]() {})); |
return true; |
} |
@@ -203,6 +213,12 @@ bool ArcVoiceInteractionFrameworkService::CanHandleAccelerators() const { |
void ArcVoiceInteractionFrameworkService::CaptureFocusedWindow( |
const CaptureFocusedWindowCallback& callback) { |
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!ValidateTimeSinceUserInteraction()) { |
+ callback.Run(std::vector<uint8_t>{}); |
+ return; |
+ } |
+ |
aura::Window* window = |
ash::Shell::Get()->activation_client()->GetActiveWindow(); |
@@ -220,6 +236,12 @@ void ArcVoiceInteractionFrameworkService::CaptureFocusedWindow( |
void ArcVoiceInteractionFrameworkService::CaptureFullscreen( |
const CaptureFullscreenCallback& callback) { |
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!ValidateTimeSinceUserInteraction()) { |
+ callback.Run(std::vector<uint8_t>{}); |
+ return; |
+ } |
+ |
// Since ARC currently only runs in primary display, we restrict |
// the screenshot to it. |
aura::Window* window = ash::Shell::GetPrimaryRootWindow(); |
@@ -257,7 +279,17 @@ void ArcVoiceInteractionFrameworkService::ShowMetalayer( |
LOG(ERROR) << "Metalayer is already enabled"; |
return; |
} |
- metalayer_closed_callback_ = closed; |
+ metalayer_closed_callback_ = base::Bind( |
+ [](const base::Callback<bool()>& init, const base::Closure& closed) { |
+ // Initiate user interaction when metalayer disappears. |
+ if (init.Run()) |
+ closed.Run(); |
+ }, |
+ base::Bind(&ArcVoiceInteractionFrameworkService::InitiateUserInteraction, |
+ // metalayer_closed_callback_ is owned and used inside |
+ // ArcVoiceInteractionFrameworkService's member functions. |
+ base::Unretained(this)), |
+ closed); |
SetMetalayerVisibility(true); |
} |
@@ -271,19 +303,6 @@ void ArcVoiceInteractionFrameworkService::HideMetalayer() { |
SetMetalayerVisibility(false); |
} |
-void ArcVoiceInteractionFrameworkService::StartVoiceInteractionSession() { |
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
- mojom::VoiceInteractionFrameworkInstance* framework_instance = |
- ARC_GET_INSTANCE_FOR_METHOD( |
- arc_bridge_service()->voice_interaction_framework(), |
- StartVoiceInteractionSession); |
- if (!framework_instance) { |
- arc::SetArcCpuRestriction(false); |
- return; |
- } |
- framework_instance->StartVoiceInteractionSession(); |
-} |
- |
void ArcVoiceInteractionFrameworkService::SetMetalayerVisibility(bool visible) { |
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
mojom::VoiceInteractionFrameworkInstance* framework_instance = |
@@ -298,4 +317,77 @@ void ArcVoiceInteractionFrameworkService::SetMetalayerVisibility(bool visible) { |
framework_instance->SetMetalayerVisibility(visible); |
} |
+void ArcVoiceInteractionFrameworkService::StartSessionFromUserInteraction( |
+ const gfx::Rect& rect) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!arc_bridge_service()->voice_interaction_framework()->has_instance()) { |
+ SetArcCpuRestriction(false); |
+ return; |
+ } |
+ |
+ if (!InitiateUserInteraction()) |
+ return; |
+ |
+ if (rect.IsEmpty()) { |
+ mojom::VoiceInteractionFrameworkInstance* framework_instance = |
+ ARC_GET_INSTANCE_FOR_METHOD( |
+ arc_bridge_service()->voice_interaction_framework(), |
+ StartVoiceInteractionSession); |
+ DCHECK(framework_instance); |
+ framework_instance->StartVoiceInteractionSession(); |
+ } else { |
+ mojom::VoiceInteractionFrameworkInstance* framework_instance = |
+ ARC_GET_INSTANCE_FOR_METHOD( |
+ arc_bridge_service()->voice_interaction_framework(), |
+ StartVoiceInteractionSessionForRegion); |
+ DCHECK(framework_instance); |
+ framework_instance->StartVoiceInteractionSessionForRegion(rect); |
+ } |
+} |
+ |
+bool ArcVoiceInteractionFrameworkService::ValidateTimeSinceUserInteraction() { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!context_request_remaining_count_) { |
+ // Allowed number of requests used up. But we still have additional |
+ // requests. It's likely that there is something malicious going on. |
+ LOG(ERROR) << "Illegal context request from container."; |
+ UMA_HISTOGRAM_BOOLEAN("VoiceInteraction.IllegalContextRequest", true); |
+ return false; |
+ } |
+ auto elapsed = base::TimeTicks::Now() - user_interaction_start_time_; |
+ elapsed = elapsed > kMaxTimeSinceUserInteractionForHistogram |
+ ? kMaxTimeSinceUserInteractionForHistogram |
+ : elapsed; |
+ |
+ UMA_HISTOGRAM_CUSTOM_COUNTS( |
+ "VoiceInteraction.UserInteractionToRequestArrival", |
+ elapsed.InMilliseconds(), 1, |
+ kMaxTimeSinceUserInteractionForHistogram.InMilliseconds(), 20); |
+ |
+ if (elapsed > kAllowedTimeSinceUserInteraction) { |
+ LOG(ERROR) << "Timed out since last user interaction."; |
+ context_request_remaining_count_ = 0; |
+ return false; |
+ } |
+ |
+ context_request_remaining_count_--; |
+ return true; |
+} |
+ |
+bool ArcVoiceInteractionFrameworkService::InitiateUserInteraction() { |
+ auto start_time = base::TimeTicks::Now(); |
+ if ((start_time - user_interaction_start_time_) < |
+ kAllowedTimeSinceUserInteraction && |
+ context_request_remaining_count_) { |
+ // If next request starts too soon and there is an active session in action, |
+ // we should drop it. |
+ return false; |
+ } |
+ user_interaction_start_time_ = start_time; |
+ context_request_remaining_count_ = kContextRequestMaxRemainingCount; |
+ return true; |
+} |
+ |
} // namespace arc |