blob: f32790dd22ceb90befbdf9ae7836d28d37f121a1 [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001// 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.
4
5#include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
6
Ben Murdoch2385ea32013-08-06 11:01:04 +01007#include "apps/native_app_window.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00008#include "ash/wm/window_util.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01009#include "chrome/browser/extensions/extension_process_manager.h"
10#include "chrome/browser/extensions/extension_system.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000011#include "chrome/browser/favicon/favicon_tab_helper.h"
12#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
13#include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
14#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
15#include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_per_app.h"
16#include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
17#include "chrome/browser/ui/browser.h"
18#include "chrome/browser/ui/browser_finder.h"
19#include "chrome/browser/ui/browser_list.h"
20#include "chrome/browser/ui/browser_window.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000021#include "chrome/browser/ui/host_desktop.h"
22#include "chrome/browser/ui/tabs/tab_strip_model.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010023#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000024#include "content/public/browser/web_contents.h"
25#include "ui/aura/window.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010026#include "ui/base/events/event.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010027#include "ui/views/corewm/window_animations.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000028
29using extensions::Extension;
30
Ben Murdocheb525c52013-07-10 11:40:50 +010031namespace {
32
33// The time delta between clicks in which clicks to launch V2 apps are ignored.
34const int kClickSuppressionInMS = 1000;
35
36} // namespace
37
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000038// Item controller for an app shortcut. Shortcuts track app and launcher ids,
39// but do not have any associated windows (opening a shortcut will replace the
40// item with the appropriate LauncherItemController type).
41AppShortcutLauncherItemController::AppShortcutLauncherItemController(
42 const std::string& app_id,
43 ChromeLauncherControllerPerApp* controller)
44 : LauncherItemController(TYPE_SHORTCUT, app_id, controller),
45 app_controller_(controller) {
46 // To detect V1 applications we use their domain and match them against the
47 // used URL. This will also work with applications like Google Drive.
48 const Extension* extension =
49 launcher_controller()->GetExtensionForAppID(app_id);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010050 // Some unit tests have no real extension.
51 if (extension) {
52 set_refocus_url(GURL(
53 extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"));
54 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000055}
56
57AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
58}
59
60string16 AppShortcutLauncherItemController::GetTitle() {
61 return GetAppTitle();
62}
63
64bool AppShortcutLauncherItemController::HasWindow(aura::Window* window) const {
65 std::vector<content::WebContents*> content =
66 app_controller_->GetV1ApplicationsFromAppId(app_id());
67 for (size_t i = 0; i < content.size(); i++) {
68 Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
69 if (browser && browser->window()->GetNativeWindow() == window)
70 return true;
71 }
72 return false;
73}
74
75bool AppShortcutLauncherItemController::IsOpen() const {
76 return !app_controller_->GetV1ApplicationsFromAppId(app_id()).empty();
77}
78
79bool AppShortcutLauncherItemController::IsVisible() const {
80 // Return true if any browser window associated with the app is visible.
81 std::vector<content::WebContents*> content =
82 app_controller_->GetV1ApplicationsFromAppId(app_id());
83 for (size_t i = 0; i < content.size(); i++) {
84 Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
85 if (browser && browser->window()->GetNativeWindow()->IsVisible())
86 return true;
87 }
88 return false;
89}
90
91void AppShortcutLauncherItemController::Launch(int event_flags) {
92 app_controller_->LaunchApp(app_id(), event_flags);
93}
94
95void AppShortcutLauncherItemController::Activate() {
96 content::WebContents* content = GetLRUApplication();
97 if (!content) {
Ben Murdocheb525c52013-07-10 11:40:50 +010098 if (IsV2App()) {
99 // Ideally we come here only once. After that ShellLauncherItemController
100 // will take over when the shell window gets opened. However there are
101 // apps which take a lot of time for pre-processing (like the files app)
102 // before they open a window. Since there is currently no other way to
103 // detect if an app was started we suppress any further clicks within a
104 // special time out.
105 if (!AllowNextLaunchAttempt())
106 return;
107 }
108 Launch(ui::EF_NONE);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000109 return;
110 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100111 ActivateContent(content);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000112}
113
114void AppShortcutLauncherItemController::Close() {
115 // Close all running 'programs' of this type.
116 std::vector<content::WebContents*> content =
117 app_controller_->GetV1ApplicationsFromAppId(app_id());
118 for (size_t i = 0; i < content.size(); i++) {
119 Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
120 if (!browser)
121 continue;
122 TabStripModel* tab_strip = browser->tab_strip_model();
123 int index = tab_strip->GetIndexOfWebContents(content[i]);
124 DCHECK(index != TabStripModel::kNoTab);
125 tab_strip->CloseWebContentsAt(index, TabStripModel::CLOSE_NONE);
126 }
127}
128
129void AppShortcutLauncherItemController::Clicked(const ui::Event& event) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100130 // In case of a keyboard event, we were called by a hotkey. In that case we
131 // activate the next item in line if an item of our list is already active.
132 if (event.type() == ui::ET_KEY_RELEASED) {
133 if (AdvanceToNextApp())
134 return;
135 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000136 Activate();
137}
138
139void AppShortcutLauncherItemController::OnRemoved() {
140 // AppShortcutLauncherItemController is unowned; delete on removal.
141 delete this;
142}
143
144void AppShortcutLauncherItemController::LauncherItemChanged(
145 int model_index,
146 const ash::LauncherItem& old_item) {
147}
148
149ChromeLauncherAppMenuItems
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100150AppShortcutLauncherItemController::GetApplicationList(int event_flags) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000151 ChromeLauncherAppMenuItems items;
152 // Add the application name to the menu.
153 items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
154
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100155 std::vector<content::WebContents*> content_list = GetRunningApplications();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000156
157 for (size_t i = 0; i < content_list.size(); i++) {
158 content::WebContents* web_contents = content_list[i];
159 // Get the icon.
160 gfx::Image app_icon = app_controller_->GetAppListIcon(web_contents);
161 string16 title = app_controller_->GetAppListTitle(web_contents);
162 items.push_back(new ChromeLauncherAppMenuItemTab(
163 title, &app_icon, web_contents, i == 0));
164 }
165 return items.Pass();
166}
167
168std::vector<content::WebContents*>
169AppShortcutLauncherItemController::GetRunningApplications() {
170 std::vector<content::WebContents*> items;
171
172 URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
173 refocus_pattern.SetMatchAllURLs(true);
174
175 if (!refocus_url_.is_empty()) {
176 refocus_pattern.SetMatchAllURLs(false);
177 refocus_pattern.Parse(refocus_url_.spec());
178 }
179
180 const Extension* extension =
181 launcher_controller()->GetExtensionForAppID(app_id());
182
183 // It is possible to come here While an extension gets loaded.
184 if (!extension)
185 return items;
186
187 const BrowserList* ash_browser_list =
188 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
189 for (BrowserList::const_iterator it = ash_browser_list->begin();
190 it != ash_browser_list->end(); ++it) {
191 Browser* browser = *it;
192 TabStripModel* tab_strip = browser->tab_strip_model();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100193 for (int index = 0; index < tab_strip->count(); index++) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000194 content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
195 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
196 items.push_back(web_contents);
197 }
198 }
199 return items;
200}
201
202content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
203 URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
204 refocus_pattern.SetMatchAllURLs(true);
205
206 if (!refocus_url_.is_empty()) {
207 refocus_pattern.SetMatchAllURLs(false);
208 refocus_pattern.Parse(refocus_url_.spec());
209 }
210
211 const Extension* extension =
212 launcher_controller()->GetExtensionForAppID(app_id());
213
214 // We may get here while the extension is loading (and NULL).
215 if (!extension)
216 return NULL;
217
218 const BrowserList* ash_browser_list =
219 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
220 for (BrowserList::const_reverse_iterator
221 it = ash_browser_list->begin_last_active();
222 it != ash_browser_list->end_last_active(); ++it) {
223 Browser* browser = *it;
224 TabStripModel* tab_strip = browser->tab_strip_model();
225 // We start to enumerate from the active index.
226 int active_index = tab_strip->active_index();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100227 for (int index = 0; index < tab_strip->count(); index++) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000228 content::WebContents* web_contents = tab_strip->GetWebContentsAt(
229 (index + active_index) % tab_strip->count());
230 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
231 return web_contents;
232 }
233 }
Ben Murdochbb1529c2013-08-08 10:24:53 +0100234 // Coming here our application was not in the LRU list. This could have
235 // happened because it did never get activated yet. So check the browser list
236 // as well.
237 for (BrowserList::const_iterator it = ash_browser_list->begin();
238 it != ash_browser_list->end(); ++it) {
239 Browser* browser = *it;
240 TabStripModel* tab_strip = browser->tab_strip_model();
241 for (int index = 0; index < tab_strip->count(); index++) {
242 content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
243 if (WebContentMatchesApp(extension, refocus_pattern, web_contents))
244 return web_contents;
245 }
246 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000247 return NULL;
248}
249
250bool AppShortcutLauncherItemController::WebContentMatchesApp(
251 const extensions::Extension* extension,
252 const URLPattern& refocus_pattern,
253 content::WebContents* web_contents) {
254 const GURL tab_url = web_contents->GetURL();
255 // There are three ways to identify the association of a URL with this
256 // extension:
257 // - The refocus pattern is matched (needed for apps like drive).
258 // - The extension's origin + extent gets matched.
259 // - The launcher controller knows that the tab got created for this app.
260 return ((!refocus_pattern.match_all_urls() &&
261 refocus_pattern.MatchesURL(tab_url)) ||
262 (extension->OverlapsWithOrigin(tab_url) &&
263 extension->web_extent().MatchesURL(tab_url)) ||
264 launcher_controller()->GetPerAppInterface()->
265 IsWebContentHandledByApplication(web_contents, app_id()));
266}
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100267
268void AppShortcutLauncherItemController::ActivateContent(
269 content::WebContents* content) {
270 Browser* browser = chrome::FindBrowserWithWebContents(content);
271 TabStripModel* tab_strip = browser->tab_strip_model();
272 int index = tab_strip->GetIndexOfWebContents(content);
273 DCHECK_NE(TabStripModel::kNoTab, index);
274
275 int old_index = tab_strip->active_index();
276 if (index != old_index)
277 tab_strip->ActivateTabAt(index, false);
278 app_controller_->ActivateWindowOrMinimizeIfActive(browser->window(),
279 index == old_index && GetRunningApplications().size() == 1);
280}
281
282bool AppShortcutLauncherItemController::AdvanceToNextApp() {
283 std::vector<content::WebContents*> items = GetRunningApplications();
284 if (items.size() >= 1) {
285 Browser* browser = chrome::FindBrowserWithWindow(
286 ash::wm::GetActiveWindow());
287 if (browser) {
288 TabStripModel* tab_strip = browser->tab_strip_model();
289 content::WebContents* active = tab_strip->GetWebContentsAt(
290 tab_strip->active_index());
291 std::vector<content::WebContents*>::const_iterator i(
292 std::find(items.begin(), items.end(), active));
293 if (i != items.end()) {
294 if (items.size() == 1) {
295 // If there is only a single item available, we animate it upon key
296 // action.
297 AnimateWindow(browser->window()->GetNativeWindow(),
298 views::corewm::WINDOW_ANIMATION_TYPE_BOUNCE);
299 } else {
300 int index = (static_cast<int>(i - items.begin()) + 1) % items.size();
301 ActivateContent(items[index]);
302 }
303 return true;
304 }
305 }
306 }
307 return false;
308}
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100309
Ben Murdocheb525c52013-07-10 11:40:50 +0100310bool AppShortcutLauncherItemController::IsV2App() {
311 const Extension* extension = app_controller_->GetExtensionForAppID(app_id());
312 return extension && extension->is_platform_app();
313}
314
315bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() {
316 if (last_launch_attempt_.is_null() ||
317 last_launch_attempt_ + base::TimeDelta::FromMilliseconds(
318 kClickSuppressionInMS) < base::Time::Now()) {
319 last_launch_attempt_ = base::Time::Now();
320 return true;
321 }
322 return false;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100323}