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