1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
7 #include "apps/ui/native_app_window.h"
8 #include "ash/launcher/launcher_model.h"
10 #include "ash/wm/window_util.h"
11 #include "chrome/browser/extensions/extension_process_manager.h"
12 #include "chrome/browser/extensions/extension_system.h"
13 #include "chrome/browser/favicon/favicon_tab_helper.h"
14 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
15 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
16 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
17 #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
18 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
19 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/host_desktop.h"
25 #include "chrome/browser/ui/tabs/tab_strip_model.h"
26 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
27 #include "content/public/browser/web_contents.h"
28 #include "ui/aura/window.h"
29 #include "ui/events/event.h"
30 #include "ui/views/corewm/window_animations.h"
32 #if defined(OS_CHROMEOS)
33 #include "chrome/browser/chromeos/login/default_pinned_apps_field_trial.h"
36 using extensions::Extension;
40 // The time delta between clicks in which clicks to launch V2 apps are ignored.
41 const int kClickSuppressionInMS = 1000;
45 // Item controller for an app shortcut. Shortcuts track app and launcher ids,
46 // but do not have any associated windows (opening a shortcut will replace the
47 // item with the appropriate LauncherItemController type).
48 AppShortcutLauncherItemController::AppShortcutLauncherItemController(
49 const std::string& app_id,
50 ChromeLauncherController* controller)
51 : LauncherItemController(TYPE_SHORTCUT, app_id, controller),
52 chrome_launcher_controller_(controller) {
53 // To detect V1 applications we use their domain and match them against the
54 // used URL. This will also work with applications like Google Drive.
55 const Extension* extension =
56 launcher_controller()->GetExtensionForAppID(app_id);
57 // Some unit tests have no real extension.
60 extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"));
64 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
67 bool AppShortcutLauncherItemController::IsCurrentlyShownInWindow(
68 aura::Window* window) const {
69 Browser* browser = chrome::FindBrowserWithWindow(window);
70 content::WebContents* active_content_of_window =
71 browser ? browser->tab_strip_model()->GetActiveWebContents() : NULL;
73 std::vector<content::WebContents*> content =
74 chrome_launcher_controller_->GetV1ApplicationsFromAppId(app_id());
76 std::vector<content::WebContents*>::const_iterator iter =
77 std::find(content.begin(), content.end(), active_content_of_window);
79 return iter != content.end() ? true : false;
82 bool AppShortcutLauncherItemController::IsOpen() const {
83 return !chrome_launcher_controller_->
84 GetV1ApplicationsFromAppId(app_id()).empty();
87 bool AppShortcutLauncherItemController::IsVisible() const {
88 // Return true if any browser window associated with the app is visible.
89 std::vector<content::WebContents*> content =
90 chrome_launcher_controller_->GetV1ApplicationsFromAppId(app_id());
91 for (size_t i = 0; i < content.size(); i++) {
92 Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
93 if (browser && browser->window()->GetNativeWindow()->IsVisible())
99 void AppShortcutLauncherItemController::Launch(ash::LaunchSource source,
101 launcher_controller()->LaunchApp(app_id(), source, event_flags);
104 void AppShortcutLauncherItemController::Activate(ash::LaunchSource source) {
105 content::WebContents* content = GetLRUApplication();
108 // Ideally we come here only once. After that ShellLauncherItemController
109 // will take over when the shell window gets opened. However there are
110 // apps which take a lot of time for pre-processing (like the files app)
111 // before they open a window. Since there is currently no other way to
112 // detect if an app was started we suppress any further clicks within a
114 if (!AllowNextLaunchAttempt())
117 Launch(source, ui::EF_NONE);
120 ActivateContent(content);
123 void AppShortcutLauncherItemController::Close() {
124 // Close all running 'programs' of this type.
125 std::vector<content::WebContents*> content =
126 launcher_controller()->GetV1ApplicationsFromAppId(app_id());
127 for (size_t i = 0; i < content.size(); i++) {
128 Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
129 if (!browser || !launcher_controller()->IsBrowserFromActiveUser(browser))
131 TabStripModel* tab_strip = browser->tab_strip_model();
132 int index = tab_strip->GetIndexOfWebContents(content[i]);
133 DCHECK(index != TabStripModel::kNoTab);
134 tab_strip->CloseWebContentsAt(index, TabStripModel::CLOSE_NONE);
138 ChromeLauncherAppMenuItems
139 AppShortcutLauncherItemController::GetApplicationList(int event_flags) {
140 ChromeLauncherAppMenuItems items;
141 // Add the application name to the menu.
142 items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
144 std::vector<content::WebContents*> content_list = GetRunningApplications();
146 for (size_t i = 0; i < content_list.size(); i++) {
147 content::WebContents* web_contents = content_list[i];
149 gfx::Image app_icon = launcher_controller()->GetAppListIcon(web_contents);
150 string16 title = launcher_controller()->GetAppListTitle(web_contents);
151 items.push_back(new ChromeLauncherAppMenuItemTab(
152 title, &app_icon, web_contents, i == 0));
157 std::vector<content::WebContents*>
158 AppShortcutLauncherItemController::GetRunningApplications() {
159 std::vector<content::WebContents*> items;
161 URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
162 refocus_pattern.SetMatchAllURLs(true);
164 if (!refocus_url_.is_empty()) {
165 refocus_pattern.SetMatchAllURLs(false);
166 refocus_pattern.Parse(refocus_url_.spec());
169 const Extension* extension =
170 launcher_controller()->GetExtensionForAppID(app_id());
172 // It is possible to come here While an extension gets loaded.
176 const BrowserList* ash_browser_list =
177 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
178 for (BrowserList::const_iterator it = ash_browser_list->begin();
179 it != ash_browser_list->end(); ++it) {
180 Browser* browser = *it;
181 if (!launcher_controller()->IsBrowserFromActiveUser(browser))
183 TabStripModel* tab_strip = browser->tab_strip_model();
184 for (int index = 0; index < tab_strip->count(); index++) {
185 content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
186 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
187 items.push_back(web_contents);
193 void AppShortcutLauncherItemController::ItemSelected(const ui::Event& event) {
194 #if defined(OS_CHROMEOS)
195 if (!app_id().empty())
196 chromeos::default_pinned_apps_field_trial::RecordShelfAppClick(app_id());
198 // In case of a keyboard event, we were called by a hotkey. In that case we
199 // activate the next item in line if an item of our list is already active.
200 if (event.type() == ui::ET_KEY_RELEASED) {
201 if (AdvanceToNextApp())
204 Activate(ash::LAUNCH_FROM_UNKNOWN);
207 base::string16 AppShortcutLauncherItemController::GetTitle() {
208 return GetAppTitle();
211 ui::MenuModel* AppShortcutLauncherItemController::CreateContextMenu(
212 aura::Window* root_window) {
213 ash::LauncherItem item =
214 *(launcher_controller()->model()->ItemByID(launcher_id()));
215 return new LauncherContextMenu(launcher_controller(), &item, root_window);
218 ash::LauncherMenuModel*
219 AppShortcutLauncherItemController::CreateApplicationMenu(int event_flags) {
220 return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
223 bool AppShortcutLauncherItemController::IsDraggable() {
227 bool AppShortcutLauncherItemController::ShouldShowTooltip() {
231 content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
232 URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
233 refocus_pattern.SetMatchAllURLs(true);
235 if (!refocus_url_.is_empty()) {
236 refocus_pattern.SetMatchAllURLs(false);
237 refocus_pattern.Parse(refocus_url_.spec());
240 const Extension* extension =
241 launcher_controller()->GetExtensionForAppID(app_id());
243 // We may get here while the extension is loading (and NULL).
247 const BrowserList* ash_browser_list =
248 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
249 for (BrowserList::const_reverse_iterator
250 it = ash_browser_list->begin_last_active();
251 it != ash_browser_list->end_last_active(); ++it) {
252 Browser* browser = *it;
253 if (!launcher_controller()->IsBrowserFromActiveUser(browser))
255 TabStripModel* tab_strip = browser->tab_strip_model();
256 // We start to enumerate from the active index.
257 int active_index = tab_strip->active_index();
258 for (int index = 0; index < tab_strip->count(); index++) {
259 content::WebContents* web_contents = tab_strip->GetWebContentsAt(
260 (index + active_index) % tab_strip->count());
261 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
265 // Coming here our application was not in the LRU list. This could have
266 // happened because it did never get activated yet. So check the browser list
268 for (BrowserList::const_iterator it = ash_browser_list->begin();
269 it != ash_browser_list->end(); ++it) {
270 Browser* browser = *it;
271 if (!launcher_controller()->IsBrowserFromActiveUser(browser))
273 TabStripModel* tab_strip = browser->tab_strip_model();
274 for (int index = 0; index < tab_strip->count(); index++) {
275 content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
276 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
283 bool AppShortcutLauncherItemController::WebContentMatchesApp(
284 const extensions::Extension* extension,
285 const URLPattern& refocus_pattern,
286 content::WebContents* web_contents) {
287 const GURL tab_url = web_contents->GetURL();
288 // There are three ways to identify the association of a URL with this
290 // - The refocus pattern is matched (needed for apps like drive).
291 // - The extension's origin + extent gets matched.
292 // - The launcher controller knows that the tab got created for this app.
293 return ((!refocus_pattern.match_all_urls() &&
294 refocus_pattern.MatchesURL(tab_url)) ||
295 (extension->OverlapsWithOrigin(tab_url) &&
296 extension->web_extent().MatchesURL(tab_url)) ||
297 launcher_controller()->IsWebContentHandledByApplication(web_contents,
301 void AppShortcutLauncherItemController::ActivateContent(
302 content::WebContents* content) {
303 Browser* browser = chrome::FindBrowserWithWebContents(content);
304 TabStripModel* tab_strip = browser->tab_strip_model();
305 int index = tab_strip->GetIndexOfWebContents(content);
306 DCHECK_NE(TabStripModel::kNoTab, index);
308 int old_index = tab_strip->active_index();
309 if (index != old_index)
310 tab_strip->ActivateTabAt(index, false);
311 launcher_controller()->ActivateWindowOrMinimizeIfActive(
313 index == old_index && GetRunningApplications().size() == 1);
316 bool AppShortcutLauncherItemController::AdvanceToNextApp() {
317 std::vector<content::WebContents*> items = GetRunningApplications();
318 if (items.size() >= 1) {
319 Browser* browser = chrome::FindBrowserWithWindow(
320 ash::wm::GetActiveWindow());
322 TabStripModel* tab_strip = browser->tab_strip_model();
323 content::WebContents* active = tab_strip->GetWebContentsAt(
324 tab_strip->active_index());
325 std::vector<content::WebContents*>::const_iterator i(
326 std::find(items.begin(), items.end(), active));
327 if (i != items.end()) {
328 if (items.size() == 1) {
329 // If there is only a single item available, we animate it upon key
331 AnimateWindow(browser->window()->GetNativeWindow(),
332 views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
334 int index = (static_cast<int>(i - items.begin()) + 1) % items.size();
335 ActivateContent(items[index]);
344 bool AppShortcutLauncherItemController::IsV2App() {
345 const Extension* extension =
346 launcher_controller()->GetExtensionForAppID(app_id());
347 return extension && extension->is_platform_app();
350 bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() {
351 if (last_launch_attempt_.is_null() ||
352 last_launch_attempt_ + base::TimeDelta::FromMilliseconds(
353 kClickSuppressionInMS) < base::Time::Now()) {
354 last_launch_attempt_ = base::Time::Now();