Home | History | Annotate | Download | only in launcher
      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.
      4 
      5 #include "chrome/browser/ui/ash/launcher/app_shortcut_launcher_item_controller.h"
      6 
      7 #include "ash/shelf/shelf_model.h"
      8 #include "ash/shell.h"
      9 #include "ash/wm/window_util.h"
     10 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item.h"
     11 #include "chrome/browser/ui/ash/launcher/chrome_launcher_app_menu_item_tab.h"
     12 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h"
     13 #include "chrome/browser/ui/ash/launcher/launcher_application_menu_item_model.h"
     14 #include "chrome/browser/ui/ash/launcher/launcher_context_menu.h"
     15 #include "chrome/browser/ui/ash/launcher/launcher_item_controller.h"
     16 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
     17 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager.h"
     18 #include "chrome/browser/ui/browser.h"
     19 #include "chrome/browser/ui/browser_finder.h"
     20 #include "chrome/browser/ui/browser_list.h"
     21 #include "chrome/browser/ui/browser_window.h"
     22 #include "chrome/browser/ui/host_desktop.h"
     23 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     24 #include "chrome/browser/web_applications/web_app.h"
     25 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     26 #include "content/public/browser/navigation_entry.h"
     27 #include "content/public/browser/web_contents.h"
     28 #include "extensions/browser/app_window/native_app_window.h"
     29 #include "extensions/browser/extension_registry.h"
     30 #include "extensions/browser/process_manager.h"
     31 #include "ui/aura/window.h"
     32 #include "ui/events/event.h"
     33 #include "ui/wm/core/window_animations.h"
     34 
     35 using extensions::Extension;
     36 using extensions::ExtensionRegistry;
     37 
     38 namespace {
     39 
     40 // The time delta between clicks in which clicks to launch V2 apps are ignored.
     41 const int kClickSuppressionInMS = 1000;
     42 
     43 // Check if a browser can be used for activation. This addresses a special use
     44 // case in the M31 multi profile mode where a user activates a V1 app which only
     45 // exists yet on another users desktop, but he expects to get only his own app
     46 // items and not the ones from other users through activation.
     47 // TODO(skuhne): Remove this function and replace the call with
     48 // launcher_controller()->IsBrowserFromActiveUser(browser) once this experiment
     49 // goes away.
     50 bool CanBrowserBeUsedForDirectActivation(Browser* browser,
     51                                          ChromeLauncherController* launcher) {
     52   if (chrome::MultiUserWindowManager::GetMultiProfileMode() ==
     53           chrome::MultiUserWindowManager::MULTI_PROFILE_MODE_OFF)
     54     return true;
     55   return multi_user_util::IsProfileFromActiveUser(browser->profile());
     56 }
     57 
     58 }  // namespace
     59 
     60 // Item controller for an app shortcut. Shortcuts track app and launcher ids,
     61 // but do not have any associated windows (opening a shortcut will replace the
     62 // item with the appropriate LauncherItemController type).
     63 AppShortcutLauncherItemController::AppShortcutLauncherItemController(
     64     const std::string& app_id,
     65     ChromeLauncherController* controller)
     66     : LauncherItemController(TYPE_SHORTCUT, app_id, controller),
     67       chrome_launcher_controller_(controller) {
     68   // To detect V1 applications we use their domain and match them against the
     69   // used URL. This will also work with applications like Google Drive.
     70   const Extension* extension =
     71       launcher_controller()->GetExtensionForAppID(app_id);
     72   // Some unit tests have no real extension.
     73   if (extension) {
     74     set_refocus_url(GURL(
     75         extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"));
     76   }
     77 }
     78 
     79 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
     80 }
     81 
     82 bool AppShortcutLauncherItemController::IsOpen() const {
     83   return !chrome_launcher_controller_->
     84       GetV1ApplicationsFromAppId(app_id()).empty();
     85 }
     86 
     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())
     94       return true;
     95   }
     96   return false;
     97 }
     98 
     99 void AppShortcutLauncherItemController::Launch(ash::LaunchSource source,
    100                                                int event_flags) {
    101   launcher_controller()->LaunchApp(app_id(), source, event_flags);
    102 }
    103 
    104 bool AppShortcutLauncherItemController::Activate(ash::LaunchSource source) {
    105   content::WebContents* content = GetLRUApplication();
    106   if (!content) {
    107     if (IsV2App()) {
    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
    113       // special time out.
    114       if (!AllowNextLaunchAttempt())
    115         return false;
    116     }
    117     Launch(source, ui::EF_NONE);
    118     return true;
    119   }
    120   ActivateContent(content);
    121   return false;
    122 }
    123 
    124 void AppShortcutLauncherItemController::Close() {
    125   // Close all running 'programs' of this type.
    126   std::vector<content::WebContents*> content =
    127       launcher_controller()->GetV1ApplicationsFromAppId(app_id());
    128   for (size_t i = 0; i < content.size(); i++) {
    129     Browser* browser = chrome::FindBrowserWithWebContents(content[i]);
    130     if (!browser || !launcher_controller()->IsBrowserFromActiveUser(browser))
    131       continue;
    132     TabStripModel* tab_strip = browser->tab_strip_model();
    133     int index = tab_strip->GetIndexOfWebContents(content[i]);
    134     DCHECK(index != TabStripModel::kNoTab);
    135     tab_strip->CloseWebContentsAt(index, TabStripModel::CLOSE_NONE);
    136   }
    137 }
    138 
    139 ChromeLauncherAppMenuItems
    140 AppShortcutLauncherItemController::GetApplicationList(int event_flags) {
    141   ChromeLauncherAppMenuItems items;
    142   // Add the application name to the menu.
    143   items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
    144 
    145   std::vector<content::WebContents*> content_list = GetRunningApplications();
    146 
    147   for (size_t i = 0; i < content_list.size(); i++) {
    148     content::WebContents* web_contents = content_list[i];
    149     // Get the icon.
    150     gfx::Image app_icon = launcher_controller()->GetAppListIcon(web_contents);
    151     base::string16 title = launcher_controller()->GetAppListTitle(web_contents);
    152     items.push_back(new ChromeLauncherAppMenuItemTab(
    153         title, &app_icon, web_contents, i == 0));
    154   }
    155   return items.Pass();
    156 }
    157 
    158 std::vector<content::WebContents*>
    159 AppShortcutLauncherItemController::GetRunningApplications() {
    160   std::vector<content::WebContents*> items;
    161 
    162   URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
    163   refocus_pattern.SetMatchAllURLs(true);
    164 
    165   if (!refocus_url_.is_empty()) {
    166     refocus_pattern.SetMatchAllURLs(false);
    167     refocus_pattern.Parse(refocus_url_.spec());
    168   }
    169 
    170   const Extension* extension =
    171       launcher_controller()->GetExtensionForAppID(app_id());
    172 
    173   // It is possible to come here While an extension gets loaded.
    174   if (!extension)
    175     return items;
    176 
    177   const BrowserList* ash_browser_list =
    178       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
    179   for (BrowserList::const_iterator it = ash_browser_list->begin();
    180        it != ash_browser_list->end(); ++it) {
    181     Browser* browser = *it;
    182     if (!launcher_controller()->IsBrowserFromActiveUser(browser))
    183       continue;
    184     TabStripModel* tab_strip = browser->tab_strip_model();
    185     for (int index = 0; index < tab_strip->count(); index++) {
    186       content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
    187       if (WebContentMatchesApp(
    188               extension, refocus_pattern, web_contents, browser))
    189         items.push_back(web_contents);
    190     }
    191   }
    192   return items;
    193 }
    194 
    195 bool AppShortcutLauncherItemController::ItemSelected(const ui::Event& event) {
    196   // In case of a keyboard event, we were called by a hotkey. In that case we
    197   // activate the next item in line if an item of our list is already active.
    198   if (event.type() == ui::ET_KEY_RELEASED) {
    199     if (AdvanceToNextApp())
    200       return false;
    201   }
    202   return Activate(ash::LAUNCH_FROM_UNKNOWN);
    203 }
    204 
    205 base::string16 AppShortcutLauncherItemController::GetTitle() {
    206   return GetAppTitle();
    207 }
    208 
    209 ui::MenuModel* AppShortcutLauncherItemController::CreateContextMenu(
    210     aura::Window* root_window) {
    211   ash::ShelfItem item =
    212       *(launcher_controller()->model()->ItemByID(shelf_id()));
    213   return new LauncherContextMenu(launcher_controller(), &item, root_window);
    214 }
    215 
    216 ash::ShelfMenuModel* AppShortcutLauncherItemController::CreateApplicationMenu(
    217     int event_flags) {
    218   return new LauncherApplicationMenuItemModel(GetApplicationList(event_flags));
    219 }
    220 
    221 bool AppShortcutLauncherItemController::IsDraggable() {
    222   return true;
    223 }
    224 
    225 bool AppShortcutLauncherItemController::ShouldShowTooltip() {
    226   return true;
    227 }
    228 
    229 content::WebContents* AppShortcutLauncherItemController::GetLRUApplication() {
    230   URLPattern refocus_pattern(URLPattern::SCHEME_ALL);
    231   refocus_pattern.SetMatchAllURLs(true);
    232 
    233   if (!refocus_url_.is_empty()) {
    234     refocus_pattern.SetMatchAllURLs(false);
    235     refocus_pattern.Parse(refocus_url_.spec());
    236   }
    237 
    238   const Extension* extension =
    239       launcher_controller()->GetExtensionForAppID(app_id());
    240 
    241   // We may get here while the extension is loading (and NULL).
    242   if (!extension)
    243     return NULL;
    244 
    245   const BrowserList* ash_browser_list =
    246       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH);
    247   for (BrowserList::const_reverse_iterator
    248        it = ash_browser_list->begin_last_active();
    249        it != ash_browser_list->end_last_active(); ++it) {
    250     Browser* browser = *it;
    251     if (!CanBrowserBeUsedForDirectActivation(browser, launcher_controller()))
    252       continue;
    253     TabStripModel* tab_strip = browser->tab_strip_model();
    254     // We start to enumerate from the active index.
    255     int active_index = tab_strip->active_index();
    256     for (int index = 0; index < tab_strip->count(); index++) {
    257       content::WebContents* web_contents = tab_strip->GetWebContentsAt(
    258           (index + active_index) % tab_strip->count());
    259       if (WebContentMatchesApp(
    260               extension, refocus_pattern, web_contents, browser))
    261         return web_contents;
    262     }
    263   }
    264   // Coming here our application was not in the LRU list. This could have
    265   // happened because it did never get activated yet. So check the browser list
    266   // as well.
    267   for (BrowserList::const_iterator it = ash_browser_list->begin();
    268        it != ash_browser_list->end(); ++it) {
    269     Browser* browser = *it;
    270     if (!CanBrowserBeUsedForDirectActivation(browser, launcher_controller()))
    271       continue;
    272     TabStripModel* tab_strip = browser->tab_strip_model();
    273     for (int index = 0; index < tab_strip->count(); index++) {
    274       content::WebContents* web_contents = tab_strip->GetWebContentsAt(index);
    275       if (WebContentMatchesApp(
    276               extension, refocus_pattern, web_contents, browser))
    277         return web_contents;
    278     }
    279   }
    280   return NULL;
    281 }
    282 
    283 bool AppShortcutLauncherItemController::WebContentMatchesApp(
    284     const extensions::Extension* extension,
    285     const URLPattern& refocus_pattern,
    286     content::WebContents* web_contents,
    287     Browser* browser) {
    288   // If the browser is an app window and the app name matches the extension.
    289   if (browser->is_app()) {
    290     const extensions::Extension* browser_extension =
    291         ExtensionRegistry::Get(browser->profile())->GetExtensionById(
    292             web_app::GetExtensionIdFromApplicationName(browser->app_name()),
    293             ExtensionRegistry::EVERYTHING);
    294     return browser_extension == extension;
    295   }
    296 
    297   // There are three ways to identify the association of a URL with this
    298   // extension:
    299   // - The refocus pattern is matched (needed for apps like drive).
    300   // - The extension's origin + extent gets matched.
    301   // - The launcher controller knows that the tab got created for this app.
    302   const GURL tab_url = web_contents->GetURL();
    303   return ((!refocus_pattern.match_all_urls() &&
    304            refocus_pattern.MatchesURL(tab_url)) ||
    305           (extension->OverlapsWithOrigin(tab_url) &&
    306            extension->web_extent().MatchesURL(tab_url)) ||
    307           launcher_controller()->IsWebContentHandledByApplication(web_contents,
    308                                                                   app_id()));
    309 }
    310 
    311 void AppShortcutLauncherItemController::ActivateContent(
    312     content::WebContents* content) {
    313   Browser* browser = chrome::FindBrowserWithWebContents(content);
    314   TabStripModel* tab_strip = browser->tab_strip_model();
    315   int index = tab_strip->GetIndexOfWebContents(content);
    316   DCHECK_NE(TabStripModel::kNoTab, index);
    317 
    318   int old_index = tab_strip->active_index();
    319   if (index != old_index)
    320     tab_strip->ActivateTabAt(index, false);
    321   launcher_controller()->ActivateWindowOrMinimizeIfActive(
    322       browser->window(),
    323       index == old_index && GetRunningApplications().size() == 1);
    324 }
    325 
    326 bool AppShortcutLauncherItemController::AdvanceToNextApp() {
    327   std::vector<content::WebContents*> items = GetRunningApplications();
    328   if (items.size() >= 1) {
    329     Browser* browser = chrome::FindBrowserWithWindow(
    330         ash::wm::GetActiveWindow());
    331     if (browser) {
    332       TabStripModel* tab_strip = browser->tab_strip_model();
    333       content::WebContents* active = tab_strip->GetWebContentsAt(
    334           tab_strip->active_index());
    335       std::vector<content::WebContents*>::const_iterator i(
    336           std::find(items.begin(), items.end(), active));
    337       if (i != items.end()) {
    338         if (items.size() == 1) {
    339           // If there is only a single item available, we animate it upon key
    340           // action.
    341           AnimateWindow(browser->window()->GetNativeWindow(),
    342               wm::WINDOW_ANIMATION_TYPE_BOUNCE);
    343         } else {
    344           int index = (static_cast<int>(i - items.begin()) + 1) % items.size();
    345           ActivateContent(items[index]);
    346         }
    347         return true;
    348       }
    349     }
    350   }
    351   return false;
    352 }
    353 
    354 bool AppShortcutLauncherItemController::IsV2App() {
    355   const Extension* extension =
    356       launcher_controller()->GetExtensionForAppID(app_id());
    357   return extension && extension->is_platform_app();
    358 }
    359 
    360 bool AppShortcutLauncherItemController::AllowNextLaunchAttempt() {
    361   if (last_launch_attempt_.is_null() ||
    362       last_launch_attempt_ + base::TimeDelta::FromMilliseconds(
    363           kClickSuppressionInMS) < base::Time::Now()) {
    364     last_launch_attempt_ = base::Time::Now();
    365     return true;
    366   }
    367   return false;
    368 }
    369