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/native_app_window.h"
      8 #include "ash/wm/window_util.h"
      9 #include "chrome/browser/extensions/extension_process_manager.h"
     10 #include "chrome/browser/extensions/extension_system.h"
     11 #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"
     21 #include "chrome/browser/ui/host_desktop.h"
     22 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     24 #include "content/public/browser/web_contents.h"
     25 #include "ui/aura/window.h"
     26 #include "ui/base/events/event.h"
     27 #include "ui/views/corewm/window_animations.h"
     28 
     29 using extensions::Extension;
     30 
     31 namespace {
     32 
     33 // The time delta between clicks in which clicks to launch V2 apps are ignored.
     34 const int kClickSuppressionInMS = 1000;
     35 
     36 }  // namespace
     37 
     38 // 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).
     41 AppShortcutLauncherItemController::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);
     50   // Some unit tests have no real extension.
     51   if (extension) {
     52     set_refocus_url(GURL(
     53         extensions::AppLaunchInfo::GetLaunchWebURL(extension).spec() + "*"));
     54   }
     55 }
     56 
     57 AppShortcutLauncherItemController::~AppShortcutLauncherItemController() {
     58 }
     59 
     60 string16 AppShortcutLauncherItemController::GetTitle() {
     61   return GetAppTitle();
     62 }
     63 
     64 bool 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 
     75 bool AppShortcutLauncherItemController::IsOpen() const {
     76   return !app_controller_->GetV1ApplicationsFromAppId(app_id()).empty();
     77 }
     78 
     79 bool 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 
     91 void AppShortcutLauncherItemController::Launch(int event_flags) {
     92   app_controller_->LaunchApp(app_id(), event_flags);
     93 }
     94 
     95 void AppShortcutLauncherItemController::Activate() {
     96   content::WebContents* content = GetLRUApplication();
     97   if (!content) {
     98     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);
    109     return;
    110   }
    111   ActivateContent(content);
    112 }
    113 
    114 void 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 
    129 void AppShortcutLauncherItemController::Clicked(const ui::Event& event) {
    130   // 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   }
    136   Activate();
    137 }
    138 
    139 void AppShortcutLauncherItemController::OnRemoved() {
    140   // AppShortcutLauncherItemController is unowned; delete on removal.
    141   delete this;
    142 }
    143 
    144 void AppShortcutLauncherItemController::LauncherItemChanged(
    145     int model_index,
    146     const ash::LauncherItem& old_item) {
    147 }
    148 
    149 ChromeLauncherAppMenuItems
    150 AppShortcutLauncherItemController::GetApplicationList(int event_flags) {
    151   ChromeLauncherAppMenuItems items;
    152   // Add the application name to the menu.
    153   items.push_back(new ChromeLauncherAppMenuItem(GetTitle(), NULL, false));
    154 
    155   std::vector<content::WebContents*> content_list = GetRunningApplications();
    156 
    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 
    168 std::vector<content::WebContents*>
    169 AppShortcutLauncherItemController::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();
    193     for (int index = 0; index < tab_strip->count(); index++) {
    194       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 
    202 content::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();
    227     for (int index = 0; index < tab_strip->count(); index++) {
    228       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   }
    234   // 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   }
    247   return NULL;
    248 }
    249 
    250 bool 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 }
    267 
    268 void 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 
    282 bool 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 }
    309 
    310 bool AppShortcutLauncherItemController::IsV2App() {
    311   const Extension* extension = app_controller_->GetExtensionForAppID(app_id());
    312   return extension && extension->is_platform_app();
    313 }
    314 
    315 bool 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;
    323 }
    324