Commit 6b17a19a authored by Dominick Ng's avatar Dominick Ng Committed by Commit Bot

Move the IntentPickerResponse callback to AppsNavigationThrottle.

This CL completes the refactoring of the ArcNavigationThrottle to be
app-platform agnostic by moving the callback run when the user responds
to the intent picker from ArcNavigationThrottle to
AppsNavigationThrottle. This will enable a future CL where the
AppsNavigationThrottle can insert non-ARC++ apps into the intent
picker, and then launch those apps if the user selects them.

There should be no functional changes from this CL. Two implementation
changes include:

1) the intent picker UI now returns an app-platform-generic CloseReason,
   and a boolean indicating if the user wanted to persist their choice.
   The AppsNavigationThrottle passes these to the ARC++ code, which
   interpolates them to the existing histogram buckets for metrics
   (which were formerly returned directly by the UI). This simplifies
   code in the UI layer, and moves responsibility for computing
   histogram values into the app-platform-specific code.

2) IntentPickerResponse is changed to be a OnceCallback, which removes
   some book-keeping in the UI layer for enforcing that it is only run
   once.

BUG=824598
TBR=sky@chromium.org

Change-Id: I1de4fd85c30e9ed09622ead11b709b9d6cdcfa98
Reviewed-on: https://chromium-review.googlesource.com/983197
Commit-Queue: Dominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarDavid Jacobo <djacobo@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548306}
parent 5736130a
...@@ -116,6 +116,21 @@ void AppsNavigationThrottle::ShowIntentPickerBubble(const Browser* browser, ...@@ -116,6 +116,21 @@ void AppsNavigationThrottle::ShowIntentPickerBubble(const Browser* browser,
browser, url)); browser, url));
} }
// static
void AppsNavigationThrottle::OnIntentPickerClosed(
const GURL& url,
const std::string& launch_name,
AppType app_type,
IntentPickerCloseReason close_reason,
bool should_persist) {
// Always call the ARC method as it records UMA stats.
// TODO(crbug.com/824598) consider cost of losing historical data against
// migrating the UMA metric out of the ARC namespace so the name is more
// sensible when other app platforms are added.
arc::ArcNavigationThrottle::OnIntentPickerClosed(
url, launch_name, app_type, close_reason, should_persist);
}
// static // static
bool AppsNavigationThrottle::ShouldOverrideUrlLoadingForTesting( bool AppsNavigationThrottle::ShouldOverrideUrlLoadingForTesting(
const GURL& previous_url, const GURL& previous_url,
...@@ -169,11 +184,9 @@ void AppsNavigationThrottle::ShowIntentPickerBubbleForApps( ...@@ -169,11 +184,9 @@ void AppsNavigationThrottle::ShowIntentPickerBubbleForApps(
if (apps.empty()) if (apps.empty())
return; return;
// TODO(crbug.com/824598): move the IntentPickerResponse callback and
// CloseReason enum/UMA to be in this class.
chrome::QueryAndDisplayArcApps( chrome::QueryAndDisplayArcApps(
browser, apps, browser, apps,
base::Bind(&arc::ArcNavigationThrottle::OnIntentPickerClosed, url)); base::BindOnce(&AppsNavigationThrottle::OnIntentPickerClosed, url));
} }
void AppsNavigationThrottle::CancelNavigation() { void AppsNavigationThrottle::CancelNavigation() {
......
...@@ -45,6 +45,17 @@ class AppsNavigationThrottle : public content::NavigationThrottle { ...@@ -45,6 +45,17 @@ class AppsNavigationThrottle : public content::NavigationThrottle {
// bubble in |browser|. // bubble in |browser|.
static void ShowIntentPickerBubble(const Browser* browser, const GURL& url); static void ShowIntentPickerBubble(const Browser* browser, const GURL& url);
// Called when the intent picker is closed for |url|, with |launch_name| as
// the (possibly empty) action to be triggered based on |app_type|.
// |close_reason| gives the reason for the picker being closed, and
// |should_persist| is true if the user indicated they wish to remember the
// choice made.
static void OnIntentPickerClosed(const GURL& url,
const std::string& launch_name,
AppType app_type,
IntentPickerCloseReason close_reason,
bool should_persist);
static bool ShouldOverrideUrlLoadingForTesting(const GURL& previous_url, static bool ShouldOverrideUrlLoadingForTesting(const GURL& previous_url,
const GURL& current_url); const GURL& current_url);
......
...@@ -13,10 +13,32 @@ ...@@ -13,10 +13,32 @@
namespace chromeos { namespace chromeos {
enum class AppType { enum class AppType {
// Used for error scenarios and other cases where the app type isn't going to
// be used (e.g. not launching an app).
INVALID,
// An Android app. // An Android app.
ARC, ARC,
}; };
// Describes the possible ways for the intent picker to be closed.
enum class IntentPickerCloseReason {
// There was an error in showing the intent picker.
ERROR,
// The user dismissed the picker without making a choice.
DIALOG_DEACTIVATED,
// A preferred app was found for launch.
PREFERRED_APP_FOUND,
// The user chose to stay in Chrome.
STAY_IN_CHROME,
// The user chose to open an app.
OPEN_APP,
};
enum class AppsNavigationAction { enum class AppsNavigationAction {
// The current navigation should be cancelled. // The current navigation should be cancelled.
CANCEL, CANCEL,
......
...@@ -257,7 +257,9 @@ void OnIntentPickerClosed(int render_process_host_id, ...@@ -257,7 +257,9 @@ void OnIntentPickerClosed(int render_process_host_id,
const GURL& url, const GURL& url,
std::vector<mojom::IntentHandlerInfoPtr> handlers, std::vector<mojom::IntentHandlerInfoPtr> handlers,
const std::string& selected_app_package, const std::string& selected_app_package,
ArcNavigationThrottle::CloseReason close_reason) { chromeos::AppType app_type,
chromeos::IntentPickerCloseReason reason,
bool should_persist) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// If the user selected an app to continue the navigation, confirm that the // If the user selected an app to continue the navigation, confirm that the
...@@ -273,18 +275,13 @@ void OnIntentPickerClosed(int render_process_host_id, ...@@ -273,18 +275,13 @@ void OnIntentPickerClosed(int render_process_host_id,
arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl); arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl);
} }
if (!instance) { if (!instance)
close_reason = ArcNavigationThrottle::CloseReason::ERROR; reason = chromeos::IntentPickerCloseReason::ERROR;
} else if (close_reason == ArcNavigationThrottle::CloseReason::
ARC_APP_PREFERRED_PRESSED || if (reason == chromeos::IntentPickerCloseReason::OPEN_APP ||
close_reason == reason == chromeos::IntentPickerCloseReason::STAY_IN_CHROME) {
ArcNavigationThrottle::CloseReason::ARC_APP_PRESSED ||
close_reason ==
ArcNavigationThrottle::CloseReason::CHROME_PREFERRED_PRESSED ||
close_reason ==
ArcNavigationThrottle::CloseReason::CHROME_PRESSED) {
if (selected_app_index == handlers.size()) { if (selected_app_index == handlers.size()) {
close_reason = ArcNavigationThrottle::CloseReason::ERROR; reason = chromeos::IntentPickerCloseReason::ERROR;
} else { } else {
// The user has made a selection. Clear g_last_* variables. // The user has made a selection. Clear g_last_* variables.
g_last_url.Get() = GURL(); g_last_url.Get() = GURL();
...@@ -292,54 +289,49 @@ void OnIntentPickerClosed(int render_process_host_id, ...@@ -292,54 +289,49 @@ void OnIntentPickerClosed(int render_process_host_id,
} }
} }
switch (close_reason) { switch (reason) {
case ArcNavigationThrottle::CloseReason::ARC_APP_PREFERRED_PRESSED: { case chromeos::IntentPickerCloseReason::OPEN_APP:
// Only ARC apps are offered in the external protocol intent picker, so if
// the user decided to open in app the type must be ARC.
DCHECK_EQ(chromeos::AppType::ARC, app_type);
DCHECK(arc_service_manager); DCHECK(arc_service_manager);
if (ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(), if (should_persist) {
AddPreferredPackage)) { if (ARC_GET_INSTANCE_FOR_METHOD(
instance->AddPreferredPackage( arc_service_manager->arc_bridge_service()->intent_helper(),
handlers[selected_app_index]->package_name); AddPreferredPackage)) {
instance->AddPreferredPackage(
handlers[selected_app_index]->package_name);
}
} }
FALLTHROUGH;
}
case ArcNavigationThrottle::CloseReason::ARC_APP_PRESSED: {
// Launch the selected app. // Launch the selected app.
HandleUrl(render_process_host_id, routing_id, url, false, handlers, HandleUrl(render_process_host_id, routing_id, url, false, handlers,
selected_app_index, nullptr); selected_app_index, nullptr);
break; break;
} case chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND:
case ArcNavigationThrottle::CloseReason::CHROME_PREFERRED_PRESSED: // We shouldn't be here if a preferred app was found.
case ArcNavigationThrottle::CloseReason::CHROME_PRESSED: { NOTREACHED();
return; // no UMA recording.
case chromeos::IntentPickerCloseReason::STAY_IN_CHROME:
LOG(ERROR) << "Chrome is not a valid option for external protocol URLs"; LOG(ERROR) << "Chrome is not a valid option for external protocol URLs";
FALLTHROUGH;
}
case ArcNavigationThrottle::CloseReason::OBSOLETE_ALWAYS_PRESSED:
case ArcNavigationThrottle::CloseReason::OBSOLETE_JUST_ONCE_PRESSED:
case ArcNavigationThrottle::CloseReason::PREFERRED_ACTIVITY_FOUND:
case ArcNavigationThrottle::CloseReason::INVALID: {
NOTREACHED(); NOTREACHED();
return; // no UMA recording. return; // no UMA recording.
} case chromeos::IntentPickerCloseReason::ERROR:
case ArcNavigationThrottle::CloseReason::ERROR: {
LOG(ERROR) << "IntentPickerBubbleView returned CloseReason::ERROR: " LOG(ERROR) << "IntentPickerBubbleView returned CloseReason::ERROR: "
<< "instance=" << instance << "instance=" << instance
<< ", selected_app_index=" << selected_app_index << ", selected_app_index=" << selected_app_index
<< ", handlers.size=" << handlers.size(); << ", handlers.size=" << handlers.size();
FALLTHROUGH; FALLTHROUGH;
} case chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED:
case ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED: {
// The user didn't select any ARC activity. // The user didn't select any ARC activity.
OnIntentPickerDialogDeactivated(render_process_host_id, routing_id, OnIntentPickerDialogDeactivated(render_process_host_id, routing_id,
handlers); handlers);
break; break;
}
} }
ArcNavigationThrottle::Platform platform = ArcNavigationThrottle::RecordUma(selected_app_package, app_type, reason,
ArcNavigationThrottle::GetDestinationPlatform(selected_app_package, should_persist);
close_reason);
ArcNavigationThrottle::RecordUma(close_reason, platform);
} }
// Called when ARC returned activity icons for the |handlers|. // Called when ARC returned activity icons for the |handlers|.
...@@ -408,8 +400,9 @@ void OnUrlHandlerList(int render_process_host_id, ...@@ -408,8 +400,9 @@ void OnUrlHandlerList(int render_process_host_id,
handlers, handlers.size(), &result)) { handlers, handlers.size(), &result)) {
if (result == GetActionResult::HANDLE_URL_IN_ARC) { if (result == GetActionResult::HANDLE_URL_IN_ARC) {
ArcNavigationThrottle::RecordUma( ArcNavigationThrottle::RecordUma(
ArcNavigationThrottle::CloseReason::PREFERRED_ACTIVITY_FOUND, std::string(), chromeos::AppType::ARC,
ArcNavigationThrottle::Platform::ARC); chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND,
false /* should_persist */);
} }
return; // the |url| has been handled. return; // the |url| has been handled.
} }
......
...@@ -90,6 +90,57 @@ bool ArcNavigationThrottle::ShouldDeferRequest( ...@@ -90,6 +90,57 @@ bool ArcNavigationThrottle::ShouldDeferRequest(
return true; return true;
} }
// static
ArcNavigationThrottle::Platform ArcNavigationThrottle::GetDestinationPlatform(
const std::string& selected_app_package,
PickerAction picker_action) {
switch (picker_action) {
case PickerAction::ARC_APP_PRESSED:
case PickerAction::ARC_APP_PREFERRED_PRESSED:
return Platform::ARC;
case PickerAction::ERROR:
case PickerAction::DIALOG_DEACTIVATED:
case PickerAction::CHROME_PRESSED:
case PickerAction::CHROME_PREFERRED_PRESSED:
return Platform::CHROME;
default:
return ArcIntentHelperBridge::IsIntentHelperPackage(selected_app_package)
? Platform::CHROME
: Platform::ARC;
}
NOTREACHED();
return Platform::SIZE;
}
// static
ArcNavigationThrottle::PickerAction ArcNavigationThrottle::GetPickerAction(
chromeos::AppType app_type,
chromeos::IntentPickerCloseReason close_reason,
bool should_persist) {
switch (close_reason) {
case chromeos::IntentPickerCloseReason::ERROR:
return PickerAction::ERROR;
case chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED:
return PickerAction::DIALOG_DEACTIVATED;
case chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND:
return PickerAction::PREFERRED_ACTIVITY_FOUND;
case chromeos::IntentPickerCloseReason::STAY_IN_CHROME:
return should_persist ? PickerAction::CHROME_PREFERRED_PRESSED
: PickerAction::CHROME_PRESSED;
case chromeos::IntentPickerCloseReason::OPEN_APP:
switch (app_type) {
case chromeos::AppType::INVALID:
return PickerAction::INVALID;
case chromeos::AppType::ARC:
return should_persist ? PickerAction::ARC_APP_PREFERRED_PRESSED
: PickerAction::ARC_APP_PRESSED;
}
}
NOTREACHED();
return PickerAction::INVALID;
}
void ArcNavigationThrottle::OnAppCandidatesReceived( void ArcNavigationThrottle::OnAppCandidatesReceived(
content::NavigationHandle* handle, content::NavigationHandle* handle,
chromeos::AppsNavigationCallback callback, chromeos::AppsNavigationCallback callback,
...@@ -99,7 +150,9 @@ void ArcNavigationThrottle::OnAppCandidatesReceived( ...@@ -99,7 +150,9 @@ void ArcNavigationThrottle::OnAppCandidatesReceived(
// This scenario shouldn't be accessed as ArcNavigationThrottle is created // This scenario shouldn't be accessed as ArcNavigationThrottle is created
// iff there are ARC apps which can actually handle the given URL. // iff there are ARC apps which can actually handle the given URL.
DVLOG(1) << "There are no app candidates for this URL: " << url; DVLOG(1) << "There are no app candidates for this URL: " << url;
RecordUma(CloseReason::ERROR, Platform::CHROME); RecordUma(std::string(), chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::ERROR,
false /* should_persist */);
std::move(callback).Run(chromeos::AppsNavigationAction::RESUME, {}); std::move(callback).Run(chromeos::AppsNavigationAction::RESUME, {});
return; return;
} }
...@@ -128,7 +181,7 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp( ...@@ -128,7 +181,7 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp(
bool cancel_navigation = false; bool cancel_navigation = false;
const size_t index = FindPreferredApp(app_candidates, url); const size_t index = FindPreferredApp(app_candidates, url);
if (index != app_candidates.size()) { if (index != app_candidates.size()) {
CloseReason close_reason = CloseReason::PREFERRED_ACTIVITY_FOUND; auto close_reason = chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND;
const std::string package_name = app_candidates[index]->package_name; const std::string package_name = app_candidates[index]->package_name;
// Make sure that the instance at least supports HandleUrl. // Make sure that the instance at least supports HandleUrl.
...@@ -141,7 +194,7 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp( ...@@ -141,7 +194,7 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp(
} }
if (!instance) { if (!instance) {
close_reason = CloseReason::ERROR; close_reason = chromeos::IntentPickerCloseReason::ERROR;
} else if (ArcIntentHelperBridge::IsIntentHelperPackage(package_name)) { } else if (ArcIntentHelperBridge::IsIntentHelperPackage(package_name)) {
chrome::SetIntentPickerViewVisibility( chrome::SetIntentPickerViewVisibility(
chrome::FindBrowserWithWebContents(web_contents), true); chrome::FindBrowserWithWebContents(web_contents), true);
...@@ -149,9 +202,8 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp( ...@@ -149,9 +202,8 @@ bool ArcNavigationThrottle::DidLaunchPreferredArcApp(
instance->HandleUrl(url.spec(), package_name); instance->HandleUrl(url.spec(), package_name);
cancel_navigation = true; cancel_navigation = true;
} }
RecordUma(package_name, chromeos::AppType::ARC, close_reason,
Platform platform = GetDestinationPlatform(package_name, close_reason); false /* should_persist */);
RecordUma(close_reason, platform);
} }
return cancel_navigation; return cancel_navigation;
} }
...@@ -167,7 +219,9 @@ void ArcNavigationThrottle::ArcAppIconQuery( ...@@ -167,7 +219,9 @@ void ArcNavigationThrottle::ArcAppIconQuery(
web_contents->GetBrowserContext()); web_contents->GetBrowserContext());
if (!intent_helper_bridge) { if (!intent_helper_bridge) {
LOG(ERROR) << "Cannot get an instance of ArcIntentHelperBridge"; LOG(ERROR) << "Cannot get an instance of ArcIntentHelperBridge";
RecordUma(CloseReason::ERROR, Platform::CHROME); RecordUma(std::string(), chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::ERROR,
false /* should_persist */);
std::move(callback).Run({}); std::move(callback).Run({});
return; return;
} }
...@@ -209,61 +263,36 @@ void ArcNavigationThrottle::OnAppIconsReceived( ...@@ -209,61 +263,36 @@ void ArcNavigationThrottle::OnAppIconsReceived(
void ArcNavigationThrottle::OnIntentPickerClosed( void ArcNavigationThrottle::OnIntentPickerClosed(
const GURL& url, const GURL& url,
const std::string& pkg, const std::string& pkg,
arc::ArcNavigationThrottle::CloseReason close_reason) { chromeos::AppType app_type,
chromeos::IntentPickerCloseReason reason,
bool should_persist) {
auto* arc_service_manager = arc::ArcServiceManager::Get(); auto* arc_service_manager = arc::ArcServiceManager::Get();
arc::mojom::IntentHelperInstance* instance = nullptr; arc::mojom::IntentHelperInstance* instance = nullptr;
if (arc_service_manager) { if (arc_service_manager) {
instance = ARC_GET_INSTANCE_FOR_METHOD( instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl); arc_service_manager->arc_bridge_service()->intent_helper(), HandleUrl);
} }
if (!instance) if (!instance)
close_reason = CloseReason::ERROR; reason = chromeos::IntentPickerCloseReason::ERROR;
switch (close_reason) { if (reason == chromeos::IntentPickerCloseReason::STAY_IN_CHROME ||
case CloseReason::ERROR: (reason == chromeos::IntentPickerCloseReason::OPEN_APP &&
case CloseReason::CHROME_PRESSED: app_type == chromeos::AppType::ARC)) {
case CloseReason::DIALOG_DEACTIVATED: { if (should_persist) {
break;
}
case CloseReason::PREFERRED_ACTIVITY_FOUND: {
if (!arc::ArcIntentHelperBridge::IsIntentHelperPackage(pkg))
instance->HandleUrl(url.spec(), pkg);
break;
}
case arc::ArcNavigationThrottle::CloseReason::ARC_APP_PREFERRED_PRESSED: {
DCHECK(arc_service_manager);
if (ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(),
AddPreferredPackage)) {
instance->AddPreferredPackage(pkg);
}
instance->HandleUrl(url.spec(), pkg);
break;
}
case arc::ArcNavigationThrottle::CloseReason::CHROME_PREFERRED_PRESSED: {
DCHECK(arc_service_manager); DCHECK(arc_service_manager);
if (ARC_GET_INSTANCE_FOR_METHOD( if (ARC_GET_INSTANCE_FOR_METHOD(
arc_service_manager->arc_bridge_service()->intent_helper(), arc_service_manager->arc_bridge_service()->intent_helper(),
AddPreferredPackage)) { AddPreferredPackage)) {
instance->AddPreferredPackage(pkg); instance->AddPreferredPackage(pkg);
} }
break;
}
case arc::ArcNavigationThrottle::CloseReason::ARC_APP_PRESSED: {
instance->HandleUrl(url.spec(), pkg);
break;
}
case arc::ArcNavigationThrottle::CloseReason::OBSOLETE_ALWAYS_PRESSED:
case arc::ArcNavigationThrottle::CloseReason::OBSOLETE_JUST_ONCE_PRESSED:
case arc::ArcNavigationThrottle::CloseReason::INVALID: {
NOTREACHED();
return;
} }
} }
arc::ArcNavigationThrottle::Platform platform = if (reason == chromeos::IntentPickerCloseReason::OPEN_APP)
arc::ArcNavigationThrottle::GetDestinationPlatform(pkg, close_reason); instance->HandleUrl(url.spec(), pkg);
arc::ArcNavigationThrottle::RecordUma(close_reason, platform);
RecordUma(pkg, app_type, reason, should_persist);
} }
// static // static
...@@ -278,26 +307,19 @@ size_t ArcNavigationThrottle::GetAppIndex( ...@@ -278,26 +307,19 @@ size_t ArcNavigationThrottle::GetAppIndex(
} }
// static // static
ArcNavigationThrottle::Platform ArcNavigationThrottle::GetDestinationPlatform( void ArcNavigationThrottle::RecordUma(
const std::string& selected_app_package, const std::string& selected_app_package,
CloseReason close_reason) { chromeos::AppType app_type,
return (close_reason != CloseReason::ERROR && chromeos::IntentPickerCloseReason close_reason,
close_reason != CloseReason::DIALOG_DEACTIVATED && bool should_persist) {
!ArcIntentHelperBridge::IsIntentHelperPackage(selected_app_package)) PickerAction action = GetPickerAction(app_type, close_reason, should_persist);
? Platform::ARC Platform platform = GetDestinationPlatform(selected_app_package, action);
: Platform::CHROME;
}
// static UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerAction", action,
void ArcNavigationThrottle::RecordUma(CloseReason close_reason, PickerAction::SIZE);
Platform platform) {
UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerAction", UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerDestinationPlatform", platform,
static_cast<int>(close_reason), Platform::SIZE);
static_cast<int>(CloseReason::SIZE));
UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerDestinationPlatform",
static_cast<int>(platform),
static_cast<int>(Platform::SIZE));
} }
// static // static
......
...@@ -28,10 +28,63 @@ namespace arc { ...@@ -28,10 +28,63 @@ namespace arc {
// traffic initiated on Chrome browser, either on Chrome or an ARC's app. // traffic initiated on Chrome browser, either on Chrome or an ARC's app.
class ArcNavigationThrottle { class ArcNavigationThrottle {
public: public:
ArcNavigationThrottle();
~ArcNavigationThrottle();
// Returns true if the navigation request represented by |handle| should be
// deferred while ARC is queried for apps, and if so, |callback| will be run
// asynchronously with the action for the navigation. |callback| will not be
// run if false is returned.
bool ShouldDeferRequest(content::NavigationHandle* handle,
chromeos::AppsNavigationCallback callback);
// Finds |selected_app_package| from the |app_candidates| array and returns
// the index. If the app is not found, returns |app_candidates.size()|.
static size_t GetAppIndex(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates,
const std::string& selected_app_package);
// Records intent picker usage statistics as well as whether navigations are
// continued or redirected to an app via UMA histograms.
static void RecordUma(const std::string& selected_app_package,
chromeos::AppType app_type,
chromeos::IntentPickerCloseReason close_reason,
bool should_persist);
// TODO(djacobo): Remove this function and instead stop ARC from returning
// Chrome as a valid app candidate.
// Records true if |app_candidates| contain one or more apps. When this
// function is called from OnAppCandidatesReceived, |app_candidates| always
// contains Chrome (aka intent helper), but the same function doesn't treat
// this as an app.
static bool IsAppAvailable(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates);
static bool IsAppAvailableForTesting(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates);
static size_t FindPreferredAppForTesting(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates);
static void QueryArcApps(const Browser* browser,
const GURL& url,
chromeos::QueryAppsCallback callback);
// Called to record UMA for the intent picker, and launch an ARC app if it was
// selected by the user.
static void OnIntentPickerClosed(const GURL& url,
const std::string& package_name,
chromeos::AppType app_type,
chromeos::IntentPickerCloseReason reason,
bool should_persist);
private:
FRIEND_TEST_ALL_PREFIXES(ArcNavigationThrottleTest, TestGetPickerAction);
FRIEND_TEST_ALL_PREFIXES(ArcNavigationThrottleTest,
TestGetDestinationPlatform);
// These enums are used to define the buckets for an enumerated UMA histogram // These enums are used to define the buckets for an enumerated UMA histogram
// and need to be synced with histograms.xml. This enum class should also be // and need to be synced with histograms.xml. This enum class should also be
// treated as append-only. // treated as append-only.
enum class CloseReason : int { enum class PickerAction : int {
ERROR = 0, ERROR = 0,
// DIALOG_DEACTIVATED keeps track of the user dismissing the UI via clicking // DIALOG_DEACTIVATED keeps track of the user dismissing the UI via clicking
// the close button or clicking outside of the IntentPickerBubbleView // the close button or clicking outside of the IntentPickerBubbleView
...@@ -55,7 +108,7 @@ class ArcNavigationThrottle { ...@@ -55,7 +108,7 @@ class ArcNavigationThrottle {
INVALID = SIZE, INVALID = SIZE,
}; };
// As for CloseReason, these define the buckets for an UMA histogram, so this // As for PickerAction, these define the buckets for an UMA histogram, so this
// must be treated in an append-only fashion. This helps especify where a // must be treated in an append-only fashion. This helps especify where a
// navigation will continue. // navigation will continue.
enum class Platform : int { enum class Platform : int {
...@@ -64,53 +117,21 @@ class ArcNavigationThrottle { ...@@ -64,53 +117,21 @@ class ArcNavigationThrottle {
SIZE, SIZE,
}; };
ArcNavigationThrottle();
~ArcNavigationThrottle();
// Returns true if the navigation request represented by |handle| should be
// deferred while ARC is queried for apps, and if so, |callback| will be run
// asynchronously with the action for the navigation. |callback| will not be
// run if false is returned.
bool ShouldDeferRequest(content::NavigationHandle* handle,
chromeos::AppsNavigationCallback callback);
// Finds |selected_app_package| from the |app_candidates| array and returns
// the index. If the app is not found, returns |app_candidates.size()|.
static size_t GetAppIndex(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates,
const std::string& selected_app_package);
// Determines the destination of the current navigation. We know that if the // Determines the destination of the current navigation. We know that if the
// |close_reason| is either ERROR or DIALOG_DEACTIVATED the navigation MUST // |picker_action| is either ERROR or DIALOG_DEACTIVATED the navigation MUST
// stay in Chrome, otherwise we can assume the navigation goes to ARC with the // stay in Chrome, otherwise we can assume the navigation goes to ARC with the
// exception of the |selected_app_package| being Chrome. // exception of the |selected_app_package| being Chrome.
static Platform GetDestinationPlatform( static Platform GetDestinationPlatform(
const std::string& selected_app_package, const std::string& selected_app_package,
CloseReason close_reason); PickerAction picker_action);
// Records intent picker usage statistics as well as whether navigations are
// continued or redirected to Chrome or ARC respectively, via UMA histograms.
static void RecordUma(CloseReason close_reason, Platform platform);
// TODO(djacobo): Remove this function and instead stop ARC from returning
// Chrome as a valid app candidate.
// Records true if |app_candidates| contain one or more apps. When this
// function is called from OnAppCandidatesReceived, |app_candidates| always
// contains Chrome (aka intent helper), but the same function doesn't treat
// this as an app.
static bool IsAppAvailable(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates);
static bool IsAppAvailableForTesting( // Converts the provided |app_type|, |close_reason| and |should_persist|
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates); // boolean to a PickerAction value for recording in UMA.
static size_t FindPreferredAppForTesting( static PickerAction GetPickerAction(
const std::vector<mojom::IntentHandlerInfoPtr>& app_candidates); chromeos::AppType app_type,
static void QueryArcApps(const Browser* browser, chromeos::IntentPickerCloseReason close_reason,
const GURL& url, bool should_persist);
chromeos::QueryAppsCallback callback);
static void OnIntentPickerClosed(
const GURL& url,
const std::string& package_name,
arc::ArcNavigationThrottle::CloseReason close_reason);
private:
// Determines whether we should open a preferred app or show the intent // Determines whether we should open a preferred app or show the intent
// picker. Resume/Cancel the navigation which was put in DEFER. Close the // picker. Resume/Cancel the navigation which was put in DEFER. Close the
// current tab only if we continue the navigation on ARC and the current tab // current tab only if we continue the navigation on ARC and the current tab
......
...@@ -100,58 +100,169 @@ TEST(ArcNavigationThrottleTest, TestGetAppIndex) { ...@@ -100,58 +100,169 @@ TEST(ArcNavigationThrottleTest, TestGetAppIndex) {
2u, ArcNavigationThrottle::GetAppIndex(CreateArray(3, 2), package_name)); 2u, ArcNavigationThrottle::GetAppIndex(CreateArray(3, 2), package_name));
} }
TEST(ArcNavigationThrottleTest, TestGetPickerAction) {
// Expect PickerAction::ERROR if the close_reason is ERROR.
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ERROR,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::ERROR, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ERROR,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::ERROR, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ERROR,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::ERROR, false));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ERROR,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::ERROR, false));
// Expect PickerAction::DIALOG_DEACTIVATED if the close_reason is
// DIALOG_DEACTIVATED.
EXPECT_EQ(ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, false));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, false));
// Expect PickerAction::PREFERRED_ACTIVITY_FOUND if the close_reason is
// PREFERRED_APP_FOUND.
EXPECT_EQ(ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND, false));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::PREFERRED_APP_FOUND, false));
// Expect PREFERRED depending on the value of |should_persist|, and |app_type|
// to be ignored if reason is STAY_IN_CHROME.
EXPECT_EQ(ArcNavigationThrottle::PickerAction::CHROME_PREFERRED_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::STAY_IN_CHROME, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::CHROME_PREFERRED_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::STAY_IN_CHROME, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::CHROME_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::STAY_IN_CHROME, false));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::CHROME_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::STAY_IN_CHROME, false));
// Expect PREFERRED depending on the value of |should_persist|, and
// INVALID/ARC to be chosen if reason is OPEN_APP.
EXPECT_EQ(ArcNavigationThrottle::PickerAction::INVALID,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::OPEN_APP, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ARC_APP_PREFERRED_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::OPEN_APP, true));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::INVALID,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::OPEN_APP, false));
EXPECT_EQ(ArcNavigationThrottle::PickerAction::ARC_APP_PRESSED,
ArcNavigationThrottle::GetPickerAction(
chromeos::AppType::ARC,
chromeos::IntentPickerCloseReason::OPEN_APP, false));
}
TEST(ArcNavigationThrottleTest, TestGetDestinationPlatform) { TEST(ArcNavigationThrottleTest, TestGetDestinationPlatform) {
const std::string chrome_app = const std::string chrome_app =
ArcIntentHelperBridge::kArcIntentHelperPackageName; ArcIntentHelperBridge::kArcIntentHelperPackageName;
const std::string non_chrome_app = "fake_package"; const std::string non_chrome_app = "fake_package";
// When the CloseReason is either ERROR or DIALOG_DEACTIVATED we MUST stay in // When the PickerAction is either ERROR or DIALOG_DEACTIVATED we MUST stay in
// Chrome not taking into account the selected_app_package. // Chrome not taking into account the selected_app_package.
EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME, EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
chrome_app, ArcNavigationThrottle::CloseReason::ERROR)); chrome_app, ArcNavigationThrottle::PickerAction::ERROR));
EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME, EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
non_chrome_app, ArcNavigationThrottle::CloseReason::ERROR)); non_chrome_app, ArcNavigationThrottle::PickerAction::ERROR));
EXPECT_EQ( EXPECT_EQ(
ArcNavigationThrottle::Platform::CHROME, ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
chrome_app, ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED)); chrome_app, ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED));
EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME, EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
non_chrome_app, non_chrome_app,
ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED)); ArcNavigationThrottle::PickerAction::DIALOG_DEACTIVATED));
// Under any other CloseReason, stay in Chrome only if the package is Chrome. // Under any other PickerAction, stay in Chrome only if the package is Chrome.
// Otherwise redirect to ARC. // Otherwise redirect to ARC.
EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME, EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
chrome_app, chrome_app,
ArcNavigationThrottle::CloseReason::OBSOLETE_ALWAYS_PRESSED)); ArcNavigationThrottle::PickerAction::OBSOLETE_ALWAYS_PRESSED));
EXPECT_EQ( EXPECT_EQ(
ArcNavigationThrottle::Platform::CHROME, ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
chrome_app, chrome_app,
ArcNavigationThrottle::CloseReason::OBSOLETE_JUST_ONCE_PRESSED)); ArcNavigationThrottle::PickerAction::OBSOLETE_JUST_ONCE_PRESSED));
EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME, EXPECT_EQ(ArcNavigationThrottle::Platform::CHROME,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
chrome_app, chrome_app,
ArcNavigationThrottle::CloseReason::PREFERRED_ACTIVITY_FOUND)); ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND));
// Go to ARC on any other case. // Go to ARC on any other case.
EXPECT_EQ(ArcNavigationThrottle::Platform::ARC, EXPECT_EQ(ArcNavigationThrottle::Platform::ARC,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
non_chrome_app, non_chrome_app,
ArcNavigationThrottle::CloseReason::OBSOLETE_ALWAYS_PRESSED)); ArcNavigationThrottle::PickerAction::OBSOLETE_ALWAYS_PRESSED));
EXPECT_EQ( EXPECT_EQ(
ArcNavigationThrottle::Platform::ARC, ArcNavigationThrottle::Platform::ARC,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
non_chrome_app, non_chrome_app,
ArcNavigationThrottle::CloseReason::OBSOLETE_JUST_ONCE_PRESSED)); ArcNavigationThrottle::PickerAction::OBSOLETE_JUST_ONCE_PRESSED));
EXPECT_EQ(ArcNavigationThrottle::Platform::ARC, EXPECT_EQ(ArcNavigationThrottle::Platform::ARC,
ArcNavigationThrottle::GetDestinationPlatform( ArcNavigationThrottle::GetDestinationPlatform(
non_chrome_app, non_chrome_app,
ArcNavigationThrottle::CloseReason::PREFERRED_ACTIVITY_FOUND)); ArcNavigationThrottle::PickerAction::PREFERRED_ACTIVITY_FOUND));
} }
} // namespace arc } // namespace arc
...@@ -1234,7 +1234,7 @@ void QueryAndDisplayArcApps( ...@@ -1234,7 +1234,7 @@ void QueryAndDisplayArcApps(
const Browser* browser, const Browser* browser,
const std::vector<chromeos::IntentPickerAppInfo>& app_info, const std::vector<chromeos::IntentPickerAppInfo>& app_info,
IntentPickerResponse callback) { IntentPickerResponse callback) {
browser->window()->ShowIntentPickerBubble(app_info, callback); browser->window()->ShowIntentPickerBubble(app_info, std::move(callback));
} }
void SetIntentPickerViewVisibility(Browser* browser, bool visible) { void SetIntentPickerViewVisibility(Browser* browser, bool visible) {
......
...@@ -312,14 +312,16 @@ void ShowChromeCleanerRebootPrompt( ...@@ -312,14 +312,16 @@ void ShowChromeCleanerRebootPrompt(
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
// This callback informs the package name of the app selected by the user, along // This callback informs the launch name and type of the app selected by the
// with the reason why the Bubble was closed. The string param must have a valid // user, along with the reason why the Bubble was closed and whether the
// package name, except when the CloseReason is ERROR or DIALOG_DEACTIVATED, for // decision should be persisted. When the reason is ERROR or DIALOG_DEACTIVATED,
// these cases we return a dummy value which won't be used at all and has no // the values of the launch name, app type, and persistence boolean are all
// significance. // ignored.
using IntentPickerResponse = using IntentPickerResponse =
base::Callback<void(const std::string&, base::OnceCallback<void(const std::string&,
arc::ArcNavigationThrottle::CloseReason)>; chromeos::AppType,
chromeos::IntentPickerCloseReason,
bool should_persist)>;
// TODO(djacobo): Decide whether or not refactor as base::RepeatableCallback. // TODO(djacobo): Decide whether or not refactor as base::RepeatableCallback.
// Return a pointer to the IntentPickerBubbleView::ShowBubble method, which in // Return a pointer to the IntentPickerBubbleView::ShowBubble method, which in
...@@ -332,7 +334,7 @@ using BubbleShowPtr = ...@@ -332,7 +334,7 @@ using BubbleShowPtr =
content::WebContents*, content::WebContents*,
const std::vector<chromeos::IntentPickerAppInfo>&, const std::vector<chromeos::IntentPickerAppInfo>&,
bool disable_display_in_chrome, bool disable_display_in_chrome,
const IntentPickerResponse&); IntentPickerResponse);
BubbleShowPtr ShowIntentPickerBubble(); BubbleShowPtr ShowIntentPickerBubble();
......
...@@ -1157,7 +1157,7 @@ void BrowserView::ShowUpdateChromeDialog() { ...@@ -1157,7 +1157,7 @@ void BrowserView::ShowUpdateChromeDialog() {
void BrowserView::ShowIntentPickerBubble( void BrowserView::ShowIntentPickerBubble(
const std::vector<IntentPickerBubbleView::AppInfo>& app_info, const std::vector<IntentPickerBubbleView::AppInfo>& app_info,
IntentPickerResponse callback) { IntentPickerResponse callback) {
toolbar_->ShowIntentPickerBubble(app_info, callback); toolbar_->ShowIntentPickerBubble(app_info, std::move(callback));
} }
void BrowserView::SetIntentPickerViewVisibility(bool visible) { void BrowserView::SetIntentPickerViewVisibility(bool visible) {
......
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
#include "chrome/browser/ui/views/intent_picker_bubble_view.h" #include "chrome/browser/ui/views/intent_picker_bubble_view.h"
#include "base/bind.h" #include <utility>
#include "base/i18n/rtl.h" #include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/apps/intent_helper/apps_navigation_throttle.h" #include "chrome/browser/chromeos/apps/intent_helper/apps_navigation_throttle.h"
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/views/animation/ink_drop_host_view.h" #include "ui/views/animation/ink_drop_host_view.h"
#include "ui/views/border.h" #include "ui/views/border.h"
...@@ -115,7 +114,7 @@ views::Widget* IntentPickerBubbleView::ShowBubble( ...@@ -115,7 +114,7 @@ views::Widget* IntentPickerBubbleView::ShowBubble(
content::WebContents* web_contents, content::WebContents* web_contents,
const std::vector<AppInfo>& app_info, const std::vector<AppInfo>& app_info,
bool disable_stay_in_chrome, bool disable_stay_in_chrome,
const IntentPickerResponse& intent_picker_cb) { IntentPickerResponse intent_picker_cb) {
if (intent_picker_bubble_) { if (intent_picker_bubble_) {
views::Widget* widget = views::Widget* widget =
views::BubbleDialogDelegateView::CreateBubble(intent_picker_bubble_); views::BubbleDialogDelegateView::CreateBubble(intent_picker_bubble_);
...@@ -124,13 +123,15 @@ views::Widget* IntentPickerBubbleView::ShowBubble( ...@@ -124,13 +123,15 @@ views::Widget* IntentPickerBubbleView::ShowBubble(
} }
Browser* browser = chrome::FindBrowserWithWebContents(web_contents); Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser || !BrowserView::GetBrowserViewForBrowser(browser)) { if (!browser || !BrowserView::GetBrowserViewForBrowser(browser)) {
intent_picker_cb.Run(kInvalidLaunchName, std::move(intent_picker_cb)
arc::ArcNavigationThrottle::CloseReason::ERROR); .Run(kInvalidLaunchName, chromeos::AppType::INVALID,
chromeos::IntentPickerCloseReason::ERROR, false);
return nullptr; return nullptr;
} }
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
intent_picker_bubble_ = new IntentPickerBubbleView( intent_picker_bubble_ =
app_info, intent_picker_cb, web_contents, disable_stay_in_chrome); new IntentPickerBubbleView(app_info, std::move(intent_picker_cb),
web_contents, disable_stay_in_chrome);
intent_picker_bubble_->set_margins(gfx::Insets()); intent_picker_bubble_->set_margins(gfx::Insets());
if (anchor_view) { if (anchor_view) {
...@@ -162,13 +163,13 @@ views::Widget* IntentPickerBubbleView::ShowBubble( ...@@ -162,13 +163,13 @@ views::Widget* IntentPickerBubbleView::ShowBubble(
// static // static
std::unique_ptr<IntentPickerBubbleView> std::unique_ptr<IntentPickerBubbleView>
IntentPickerBubbleView::CreateBubbleView( IntentPickerBubbleView::CreateBubbleView(const std::vector<AppInfo>& app_info,
const std::vector<AppInfo>& app_info, bool disable_stay_in_chrome,
bool disable_stay_in_chrome, IntentPickerResponse intent_picker_cb,
const IntentPickerResponse& intent_picker_cb, content::WebContents* web_contents) {
content::WebContents* web_contents) { std::unique_ptr<IntentPickerBubbleView> bubble(
std::unique_ptr<IntentPickerBubbleView> bubble(new IntentPickerBubbleView( new IntentPickerBubbleView(app_info, std::move(intent_picker_cb),
app_info, intent_picker_cb, web_contents, disable_stay_in_chrome)); web_contents, disable_stay_in_chrome));
bubble->Init(); bubble->Init();
return bubble; return bubble;
} }
...@@ -185,28 +186,26 @@ void IntentPickerBubbleView::CloseBubble() { ...@@ -185,28 +186,26 @@ void IntentPickerBubbleView::CloseBubble() {
} }
bool IntentPickerBubbleView::Accept() { bool IntentPickerBubbleView::Accept() {
RunCallback( RunCallback(app_info_[selected_app_tag_].launch_name,
app_info_[selected_app_tag_].launch_name, app_info_[selected_app_tag_].type,
remember_selection_checkbox_->checked() chromeos::IntentPickerCloseReason::OPEN_APP,
? arc::ArcNavigationThrottle::CloseReason::ARC_APP_PREFERRED_PRESSED remember_selection_checkbox_->checked());
: arc::ArcNavigationThrottle::CloseReason::ARC_APP_PRESSED);
return true; return true;
} }
bool IntentPickerBubbleView::Cancel() { bool IntentPickerBubbleView::Cancel() {
RunCallback( RunCallback(arc::ArcIntentHelperBridge::kArcIntentHelperPackageName,
arc::ArcIntentHelperBridge::kArcIntentHelperPackageName, chromeos::AppType::INVALID,
remember_selection_checkbox_->checked() chromeos::IntentPickerCloseReason::STAY_IN_CHROME,
? arc::ArcNavigationThrottle::CloseReason::CHROME_PREFERRED_PRESSED remember_selection_checkbox_->checked());
: arc::ArcNavigationThrottle::CloseReason::CHROME_PRESSED);
return true; return true;
} }
bool IntentPickerBubbleView::Close() { bool IntentPickerBubbleView::Close() {
// Whenever closing the bubble without pressing |Just once| or |Always| we // Whenever closing the bubble without pressing |Just once| or |Always| we
// need to report back that the user didn't select anything. // need to report back that the user didn't select anything.
RunCallback(kInvalidLaunchName, RunCallback(kInvalidLaunchName, chromeos::AppType::INVALID,
arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED); chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, false);
return true; return true;
} }
...@@ -310,7 +309,7 @@ IntentPickerBubbleView::IntentPickerBubbleView( ...@@ -310,7 +309,7 @@ IntentPickerBubbleView::IntentPickerBubbleView(
: LocationBarBubbleDelegateView(nullptr /* anchor_view */, : LocationBarBubbleDelegateView(nullptr /* anchor_view */,
gfx::Point(), gfx::Point(),
web_contents), web_contents),
intent_picker_cb_(intent_picker_cb), intent_picker_cb_(std::move(intent_picker_cb)),
selected_app_tag_(0), selected_app_tag_(0),
scroll_view_(nullptr), scroll_view_(nullptr),
app_info_(app_info), app_info_(app_info),
...@@ -326,8 +325,8 @@ IntentPickerBubbleView::~IntentPickerBubbleView() { ...@@ -326,8 +325,8 @@ IntentPickerBubbleView::~IntentPickerBubbleView() {
// If the widget gets closed without an app being selected we still need to use // If the widget gets closed without an app being selected we still need to use
// the callback so the caller can Resume the navigation. // the callback so the caller can Resume the navigation.
void IntentPickerBubbleView::OnWidgetDestroying(views::Widget* widget) { void IntentPickerBubbleView::OnWidgetDestroying(views::Widget* widget) {
RunCallback(kInvalidLaunchName, RunCallback(kInvalidLaunchName, chromeos::AppType::INVALID,
arc::ArcNavigationThrottle::CloseReason::DIALOG_DEACTIVATED); chromeos::IntentPickerCloseReason::DIALOG_DEACTIVATED, false);
} }
void IntentPickerBubbleView::ButtonPressed(views::Button* sender, void IntentPickerBubbleView::ButtonPressed(views::Button* sender,
...@@ -377,14 +376,13 @@ IntentPickerLabelButton* IntentPickerBubbleView::GetIntentPickerLabelButtonAt( ...@@ -377,14 +376,13 @@ IntentPickerLabelButton* IntentPickerBubbleView::GetIntentPickerLabelButtonAt(
void IntentPickerBubbleView::RunCallback( void IntentPickerBubbleView::RunCallback(
const std::string& launch_name, const std::string& launch_name,
arc::ArcNavigationThrottle::CloseReason close_reason) { chromeos::AppType app_type,
chromeos::IntentPickerCloseReason close_reason,
bool should_persist) {
if (!intent_picker_cb_.is_null()) { if (!intent_picker_cb_.is_null()) {
// We must ensure |intent_picker_cb_| is only Run() once, this is why we // Calling Run() will make |intent_picker_cb_| null.
// have a temporary |callback| helper, so we can set the original callback std::move(intent_picker_cb_)
// to null and still report back to whoever started the UI. .Run(launch_name, app_type, close_reason, should_persist);
auto callback = intent_picker_cb_;
intent_picker_cb_.Reset();
callback.Run(launch_name, close_reason);
} }
intent_picker_bubble_ = nullptr; intent_picker_bubble_ = nullptr;
......
...@@ -9,12 +9,10 @@ ...@@ -9,12 +9,10 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/callback.h"
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/chromeos/apps/intent_helper/apps_navigation_types.h" #include "chrome/browser/chromeos/apps/intent_helper/apps_navigation_types.h"
#include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h" #include "chrome/browser/ui/views/location_bar/location_bar_bubble_delegate_view.h"
#include "content/public/browser/web_contents_observer.h"
#include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/accelerator.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/button.h"
...@@ -41,8 +39,8 @@ class IntentPickerLabelButton; ...@@ -41,8 +39,8 @@ class IntentPickerLabelButton;
// outside of the bubble allows the user to dismiss the bubble (and stay in // outside of the bubble allows the user to dismiss the bubble (and stay in
// Chrome) without remembering any decision. // Chrome) without remembering any decision.
// //
// This class comunicates the user's selection with a callback used by // This class communicates the user's selection with a callback supplied by
// ArcNavigationThrottle. // AppsNavigationThrottle.
// +--------------------------------+ // +--------------------------------+
// | Open with [x] | // | Open with [x] |
// | | // | |
...@@ -62,16 +60,15 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView, ...@@ -62,16 +60,15 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView,
using AppInfo = chromeos::IntentPickerAppInfo; using AppInfo = chromeos::IntentPickerAppInfo;
~IntentPickerBubbleView() override; ~IntentPickerBubbleView() override;
static views::Widget* ShowBubble( static views::Widget* ShowBubble(views::View* anchor_view,
views::View* anchor_view, content::WebContents* web_contents,
content::WebContents* web_contents, const std::vector<AppInfo>& app_info,
const std::vector<AppInfo>& app_info, bool disable_stay_in_chrome,
bool disable_stay_in_chrome, IntentPickerResponse intent_picker_cb);
const IntentPickerResponse& intent_picker_cb);
static std::unique_ptr<IntentPickerBubbleView> CreateBubbleView( static std::unique_ptr<IntentPickerBubbleView> CreateBubbleView(
const std::vector<AppInfo>& app_info, const std::vector<AppInfo>& app_info,
bool disable_stay_in_chrome, bool disable_stay_in_chrome,
const IntentPickerResponse& intent_picker_cb, IntentPickerResponse intent_picker_cb,
content::WebContents* web_contents); content::WebContents* web_contents);
static IntentPickerBubbleView* intent_picker_bubble() { static IntentPickerBubbleView* intent_picker_bubble() {
return intent_picker_bubble_; return intent_picker_bubble_;
...@@ -125,7 +122,9 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView, ...@@ -125,7 +122,9 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView,
// the internal ScrollView. // the internal ScrollView.
IntentPickerLabelButton* GetIntentPickerLabelButtonAt(size_t index); IntentPickerLabelButton* GetIntentPickerLabelButtonAt(size_t index);
void RunCallback(const std::string& launch_name, void RunCallback(const std::string& launch_name,
arc::ArcNavigationThrottle::CloseReason close_reason); chromeos::AppType app_type,
chromeos::IntentPickerCloseReason close_reason,
bool should_persist);
// Accessory for |scroll_view_|'s contents size. // Accessory for |scroll_view_|'s contents size.
size_t GetScrollViewSize() const; size_t GetScrollViewSize() const;
...@@ -146,7 +145,7 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView, ...@@ -146,7 +145,7 @@ class IntentPickerBubbleView : public LocationBarBubbleDelegateView,
static IntentPickerBubbleView* intent_picker_bubble_; static IntentPickerBubbleView* intent_picker_bubble_;
// Callback used to respond to ArcNavigationThrottle. // Callback used to respond to AppsNavigationThrottle.
IntentPickerResponse intent_picker_cb_; IntentPickerResponse intent_picker_cb_;
// Pre-select the first app on the list. // Pre-select the first app on the list.
......
...@@ -79,7 +79,9 @@ class IntentPickerBubbleViewTest : public BrowserWithTestWindowTest { ...@@ -79,7 +79,9 @@ class IntentPickerBubbleViewTest : public BrowserWithTestWindowTest {
// Dummy method to be called upon bubble closing. // Dummy method to be called upon bubble closing.
void OnBubbleClosed(const std::string& selected_app_package, void OnBubbleClosed(const std::string& selected_app_package,
arc::ArcNavigationThrottle::CloseReason close_reason) {} chromeos::AppType app_type,
chromeos::IntentPickerCloseReason close_reason,
bool should_persist) {}
std::unique_ptr<IntentPickerBubbleView> bubble_; std::unique_ptr<IntentPickerBubbleView> bubble_;
std::vector<AppInfo> app_info_; std::vector<AppInfo> app_info_;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include <algorithm> #include <algorithm>
#include <utility>
#include "base/command_line.h" #include "base/command_line.h"
#include "base/i18n/number_formatting.h" #include "base/i18n/number_formatting.h"
...@@ -303,7 +304,7 @@ void ToolbarView::ShowIntentPickerBubble( ...@@ -303,7 +304,7 @@ void ToolbarView::ShowIntentPickerBubble(
views::Widget* bubble_widget = IntentPickerBubbleView::ShowBubble( views::Widget* bubble_widget = IntentPickerBubbleView::ShowBubble(
intent_picker_view, GetWebContents(), app_info, intent_picker_view, GetWebContents(), app_info,
false /* disable_stay_in_chrome */, callback); false /* disable_stay_in_chrome */, std::move(callback));
if (bubble_widget && intent_picker_view) if (bubble_widget && intent_picker_view)
intent_picker_view->OnBubbleWidgetCreated(bubble_widget); intent_picker_view->OnBubbleWidgetCreated(bubble_widget);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment