app_list_controller_win.cc revision 7c720b7466665b17575e0fd6976f9321c6bff489
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 <dwmapi.h>
6#include <sstream>
7
8#include "apps/pref_names.h"
9#include "base/command_line.h"
10#include "base/file_util.h"
11#include "base/lazy_instance.h"
12#include "base/memory/singleton.h"
13#include "base/memory/weak_ptr.h"
14#include "base/path_service.h"
15#include "base/prefs/pref_service.h"
16#include "base/strings/utf_string_conversions.h"
17#include "base/threading/sequenced_worker_pool.h"
18#include "base/time/time.h"
19#include "base/timer/timer.h"
20#include "base/win/shortcut.h"
21#include "base/win/windows_version.h"
22#include "chrome/app/chrome_dll_resource.h"
23#include "chrome/browser/browser_process.h"
24#include "chrome/browser/extensions/extension_service.h"
25#include "chrome/browser/extensions/extension_system.h"
26#include "chrome/browser/lifetime/application_lifetime.h"
27#include "chrome/browser/platform_util.h"
28#include "chrome/browser/profiles/profile.h"
29#include "chrome/browser/profiles/profile_manager.h"
30#include "chrome/browser/shell_integration.h"
31#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
32#include "chrome/browser/ui/app_list/app_list_service_impl.h"
33#include "chrome/browser/ui/app_list/app_list_service_win.h"
34#include "chrome/browser/ui/app_list/app_list_view_delegate.h"
35#include "chrome/browser/ui/browser_commands.h"
36#include "chrome/browser/ui/extensions/app_metro_infobar_delegate_win.h"
37#include "chrome/browser/ui/extensions/application_launch.h"
38#include "chrome/browser/ui/views/browser_dialogs.h"
39#include "chrome/browser/web_applications/web_app.h"
40#include "chrome/common/chrome_constants.h"
41#include "chrome/common/chrome_switches.h"
42#include "chrome/common/chrome_version_info.h"
43#include "chrome/common/pref_names.h"
44#include "chrome/installer/launcher_support/chrome_launcher_support.h"
45#include "chrome/installer/util/browser_distribution.h"
46#include "chrome/installer/util/google_update_settings.h"
47#include "chrome/installer/util/install_util.h"
48#include "chrome/installer/util/util_constants.h"
49#include "content/public/browser/browser_thread.h"
50#include "grit/chromium_strings.h"
51#include "grit/generated_resources.h"
52#include "grit/google_chrome_strings.h"
53#include "ui/app_list/pagination_model.h"
54#include "ui/app_list/views/app_list_view.h"
55#include "ui/base/l10n/l10n_util.h"
56#include "ui/base/resource/resource_bundle.h"
57#include "ui/base/win/shell.h"
58#include "ui/gfx/display.h"
59#include "ui/gfx/image/image_skia.h"
60#include "ui/gfx/screen.h"
61#include "ui/views/bubble/bubble_border.h"
62#include "ui/views/widget/widget.h"
63#include "win8/util/win8_util.h"
64
65#if defined(GOOGLE_CHROME_BUILD)
66#include "chrome/installer/util/install_util.h"
67#endif
68
69#if defined(USE_AURA)
70#include "ui/aura/root_window.h"
71#include "ui/aura/window.h"
72#endif
73
74namespace {
75
76// Offset from the cursor to the point of the bubble arrow. It looks weird
77// if the arrow comes up right on top of the cursor, so it is offset by this
78// amount.
79static const int kAnchorOffset = 25;
80
81static const wchar_t kTrayClassName[] = L"Shell_TrayWnd";
82
83// Migrate chrome::kAppLauncherIsEnabled pref to
84// chrome::kAppLauncherHasBeenEnabled pref.
85void MigrateAppLauncherEnabledPref() {
86  PrefService* prefs = g_browser_process->local_state();
87  if (prefs->HasPrefPath(apps::prefs::kAppLauncherIsEnabled)) {
88    prefs->SetBoolean(apps::prefs::kAppLauncherHasBeenEnabled,
89                      prefs->GetBoolean(apps::prefs::kAppLauncherIsEnabled));
90    prefs->ClearPref(apps::prefs::kAppLauncherIsEnabled);
91  }
92}
93
94// Icons are added to the resources of the DLL using icon names. The icon index
95// for the app list icon is named IDR_X_APP_LIST or (for official builds)
96// IDR_X_APP_LIST_SXS for Chrome Canary. Creating shortcuts needs to specify a
97// resource index, which are different to icon names.  They are 0 based and
98// contiguous. As Google Chrome builds have extra icons the icon for Google
99// Chrome builds need to be higher. Unfortunately these indexes are not in any
100// generated header file.
101int GetAppListIconIndex() {
102  const int kAppListIconIndex = 5;
103  const int kAppListIconIndexSxS = 6;
104  const int kAppListIconIndexChromium = 1;
105#if defined(GOOGLE_CHROME_BUILD)
106  if (InstallUtil::IsChromeSxSProcess())
107    return kAppListIconIndexSxS;
108  return kAppListIconIndex;
109#else
110  return kAppListIconIndexChromium;
111#endif
112}
113
114string16 GetAppListShortcutName() {
115  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
116  if (channel == chrome::VersionInfo::CHANNEL_CANARY)
117    return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY);
118  return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME);
119}
120
121CommandLine GetAppListCommandLine() {
122  const char* const kSwitchesToCopy[] = { switches::kUserDataDir };
123  CommandLine* current = CommandLine::ForCurrentProcess();
124  base::FilePath chrome_exe;
125  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
126     NOTREACHED();
127     return CommandLine(CommandLine::NO_PROGRAM);
128  }
129  CommandLine command_line(chrome_exe);
130  command_line.CopySwitchesFrom(*current, kSwitchesToCopy,
131                                arraysize(kSwitchesToCopy));
132  command_line.AppendSwitch(switches::kShowAppList);
133  return command_line;
134}
135
136string16 GetAppModelId() {
137  // The AppModelId should be the same for all profiles in a user data directory
138  // but different for different user data directories, so base it on the
139  // initial profile in the current user data directory.
140  base::FilePath initial_profile_path;
141  CommandLine* command_line = CommandLine::ForCurrentProcess();
142  if (command_line->HasSwitch(switches::kUserDataDir)) {
143    initial_profile_path =
144        command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII(
145            chrome::kInitialProfile);
146  }
147  return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path);
148}
149
150void SetDidRunForNDayActiveStats() {
151  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
152  base::FilePath exe_path;
153  if (!PathService::Get(base::DIR_EXE, &exe_path)) {
154    NOTREACHED();
155    return;
156  }
157  bool system_install =
158      !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
159  // Using Chrome Binary dist: Chrome dist may not exist for the legacy
160  // App Launcher, and App Launcher dist may be "shadow", which does not
161  // contain the information needed to determine multi-install.
162  // Edge case involving Canary: crbug/239163.
163  BrowserDistribution* chrome_binaries_dist =
164      BrowserDistribution::GetSpecificDistribution(
165          BrowserDistribution::CHROME_BINARIES);
166  if (chrome_binaries_dist &&
167      InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) {
168    BrowserDistribution* app_launcher_dist =
169        BrowserDistribution::GetSpecificDistribution(
170            BrowserDistribution::CHROME_APP_HOST);
171    GoogleUpdateSettings::UpdateDidRunStateForDistribution(
172        app_launcher_dist,
173        true /* did_run */,
174        system_install);
175  }
176}
177
178// The start menu shortcut is created on first run by users that are
179// upgrading. The desktop and taskbar shortcuts are created the first time the
180// user enables the app list. The taskbar shortcut is created in
181// |user_data_dir| and will use a Windows Application Model Id of
182// |app_model_id|. This runs on the FILE thread and not in the blocking IO
183// thread pool as there are other tasks running (also on the FILE thread)
184// which fiddle with shortcut icons
185// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads
186// fiddle with the same shortcuts could cause race issues.
187void CreateAppListShortcuts(
188    const base::FilePath& user_data_dir,
189    const string16& app_model_id,
190    const ShellIntegration::ShortcutLocations& creation_locations) {
191  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
192
193  // Shortcut paths under which to create shortcuts.
194  std::vector<base::FilePath> shortcut_paths =
195      web_app::internals::GetShortcutPaths(creation_locations);
196
197  bool pin_to_taskbar = creation_locations.in_quick_launch_bar &&
198                        (base::win::GetVersion() >= base::win::VERSION_WIN7);
199
200  // Create a shortcut in the |user_data_dir| for taskbar pinning.
201  if (pin_to_taskbar)
202    shortcut_paths.push_back(user_data_dir);
203  bool success = true;
204
205  base::FilePath chrome_exe;
206  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
207    NOTREACHED();
208    return;
209  }
210
211  string16 app_list_shortcut_name = GetAppListShortcutName();
212
213  string16 wide_switches(GetAppListCommandLine().GetArgumentsString());
214
215  base::win::ShortcutProperties shortcut_properties;
216  shortcut_properties.set_target(chrome_exe);
217  shortcut_properties.set_working_dir(chrome_exe.DirName());
218  shortcut_properties.set_arguments(wide_switches);
219  shortcut_properties.set_description(app_list_shortcut_name);
220  shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex());
221  shortcut_properties.set_app_id(app_model_id);
222
223  for (size_t i = 0; i < shortcut_paths.size(); ++i) {
224    base::FilePath shortcut_file =
225        shortcut_paths[i].Append(app_list_shortcut_name).
226            AddExtension(installer::kLnkExt);
227    if (!base::PathExists(shortcut_file.DirName()) &&
228        !file_util::CreateDirectory(shortcut_file.DirName())) {
229      NOTREACHED();
230      return;
231    }
232    success = success && base::win::CreateOrUpdateShortcutLink(
233        shortcut_file, shortcut_properties,
234        base::win::SHORTCUT_CREATE_ALWAYS);
235  }
236
237  if (success && pin_to_taskbar) {
238    base::FilePath shortcut_to_pin =
239        user_data_dir.Append(app_list_shortcut_name).
240            AddExtension(installer::kLnkExt);
241    success = base::win::TaskbarPinShortcutLink(
242        shortcut_to_pin.value().c_str()) && success;
243  }
244}
245
246class AppListControllerDelegateWin : public AppListControllerDelegate {
247 public:
248  AppListControllerDelegateWin();
249  virtual ~AppListControllerDelegateWin();
250
251 private:
252  // AppListController overrides:
253  virtual void DismissView() OVERRIDE;
254  virtual void ViewClosing() OVERRIDE;
255  virtual void ViewActivationChanged(bool active) OVERRIDE;
256  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE;
257  virtual gfx::ImageSkia GetWindowIcon() OVERRIDE;
258  virtual bool CanPin() OVERRIDE;
259  virtual void OnShowExtensionPrompt() OVERRIDE;
260  virtual void OnCloseExtensionPrompt() OVERRIDE;
261  virtual bool CanDoCreateShortcutsFlow(bool is_platform_app) OVERRIDE;
262  virtual void DoCreateShortcutsFlow(Profile* profile,
263                                     const std::string& extension_id) OVERRIDE;
264  virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE;
265  virtual void ActivateApp(Profile* profile,
266                           const extensions::Extension* extension,
267                           int event_flags) OVERRIDE;
268  virtual void LaunchApp(Profile* profile,
269                         const extensions::Extension* extension,
270                         int event_flags) OVERRIDE;
271
272  DISALLOW_COPY_AND_ASSIGN(AppListControllerDelegateWin);
273};
274
275class ScopedKeepAlive {
276 public:
277  ScopedKeepAlive() { chrome::StartKeepAlive(); }
278  ~ScopedKeepAlive() { chrome::EndKeepAlive(); }
279
280 private:
281  DISALLOW_COPY_AND_ASSIGN(ScopedKeepAlive);
282};
283
284class ActivationTracker {
285 public:
286  ActivationTracker(app_list::AppListView* view,
287                    const base::Closure& on_active_lost)
288      : view_(view),
289        on_active_lost_(on_active_lost),
290        regain_next_lost_focus_(false),
291        preserving_focus_for_taskbar_menu_(false) {
292  }
293
294  void RegainNextLostFocus() {
295    regain_next_lost_focus_ = true;
296  }
297
298  void OnActivationChanged(bool active) {
299    const int kFocusCheckIntervalMS = 250;
300    if (active) {
301      timer_.Stop();
302      return;
303    }
304
305    preserving_focus_for_taskbar_menu_ = false;
306    timer_.Start(FROM_HERE,
307                 base::TimeDelta::FromMilliseconds(kFocusCheckIntervalMS), this,
308                 &ActivationTracker::CheckTaskbarOrViewHasFocus);
309  }
310
311  void OnViewHidden() {
312    timer_.Stop();
313  }
314
315  void CheckTaskbarOrViewHasFocus() {
316    // Remember if the taskbar had focus without the right mouse button being
317    // down.
318    bool was_preserving_focus = preserving_focus_for_taskbar_menu_;
319    preserving_focus_for_taskbar_menu_ = false;
320
321    // First get the taskbar and jump lists windows (the jump list is the
322    // context menu which the taskbar uses).
323    HWND jump_list_hwnd = FindWindow(L"DV2ControlHost", NULL);
324    HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL);
325
326    // This code is designed to hide the app launcher when it loses focus,
327    // except for the cases necessary to allow the launcher to be pinned or
328    // closed via the taskbar context menu.
329    // First work out if the left or right button is currently down.
330    int swapped = GetSystemMetrics(SM_SWAPBUTTON);
331    int left_button = swapped ? VK_RBUTTON : VK_LBUTTON;
332    bool left_button_down = GetAsyncKeyState(left_button) < 0;
333    int right_button = swapped ? VK_LBUTTON : VK_RBUTTON;
334    bool right_button_down = GetAsyncKeyState(right_button) < 0;
335
336    // Now get the window that currently has focus.
337    HWND focused_hwnd = GetForegroundWindow();
338    if (!focused_hwnd) {
339      // Sometimes the focused window is NULL. This can happen when the focus is
340      // changing due to a mouse button press. If the button is still being
341      // pressed the launcher should not be hidden.
342      if (right_button_down || left_button_down)
343        return;
344
345      // If the focused window is NULL, and the mouse button is not being
346      // pressed, then the launcher no longer has focus.
347      on_active_lost_.Run();
348      return;
349    }
350
351    while (focused_hwnd) {
352      // If the focused window is the right click menu (called a jump list) or
353      // the app list, don't hide the launcher.
354      if (focused_hwnd == jump_list_hwnd ||
355          focused_hwnd == view_->GetHWND()) {
356        return;
357      }
358
359      if (focused_hwnd == taskbar_hwnd) {
360        // If the focused window is the taskbar, and the right button is down,
361        // don't hide the launcher as the user might be bringing up the menu.
362        if (right_button_down)
363          return;
364
365        // There is a short period between the right mouse button being down
366        // and the menu gaining focus, where the taskbar has focus and no button
367        // is down. If the taskbar is observed in this state once the launcher
368        // is not dismissed. If it happens twice in a row it is dismissed.
369        if (!was_preserving_focus) {
370          preserving_focus_for_taskbar_menu_ = true;
371          return;
372        }
373
374        break;
375      }
376      focused_hwnd = GetParent(focused_hwnd);
377    }
378
379    if (regain_next_lost_focus_) {
380      regain_next_lost_focus_ = false;
381      view_->GetWidget()->Activate();
382      return;
383    }
384
385    // If we get here, the focused window is not the taskbar, it's context menu,
386    // or the app list.
387    on_active_lost_.Run();
388  }
389
390 private:
391  // The window to track the active state of.
392  app_list::AppListView* view_;
393
394  // Called to request |view_| be closed.
395  base::Closure on_active_lost_;
396
397  // True if we are anticipating that the app list will lose focus, and we want
398  // to take it back. This is used when switching out of Metro mode, and the
399  // browser regains focus after showing the app list.
400  bool regain_next_lost_focus_;
401
402  // When the context menu on the app list's taskbar icon is brought up the
403  // app list should not be hidden, but it should be if the taskbar is clicked
404  // on. There can be a period of time when the taskbar gets focus between a
405  // right mouse click and the menu showing; to prevent hiding the app launcher
406  // when this happens it is kept visible if the taskbar is seen briefly without
407  // the right mouse button down, but not if this happens twice in a row.
408  bool preserving_focus_for_taskbar_menu_;
409
410  // Timer used to check if the taskbar or app list is active. Using a timer
411  // means we don't need to hook Windows, which is apparently not possible
412  // since Vista (and is not nice at any time).
413  base::RepeatingTimer<ActivationTracker> timer_;
414};
415
416// The AppListController class manages global resources needed for the app
417// list to operate, and controls when the app list is opened and closed.
418// TODO(tapted): Rename this class to AppListServiceWin and move entire file to
419// chrome/browser/ui/app_list/app_list_service_win.cc after removing
420// chrome/browser/ui/views dependency.
421class AppListController : public AppListServiceImpl {
422 public:
423  virtual ~AppListController();
424
425  static AppListController* GetInstance() {
426    return Singleton<AppListController,
427                     LeakySingletonTraits<AppListController> >::get();
428  }
429
430  void set_can_close(bool can_close) { can_close_app_list_ = can_close; }
431  bool can_close() { return can_close_app_list_; }
432
433  void AppListClosing();
434  void AppListActivationChanged(bool active);
435  void ShowAppListDuringModeSwitch(Profile* requested_profile);
436
437  app_list::AppListView* GetView() { return current_view_; }
438
439  // AppListService overrides:
440  virtual void HandleFirstRun() OVERRIDE;
441  virtual void Init(Profile* initial_profile) OVERRIDE;
442  virtual void CreateForProfile(Profile* requested_profile) OVERRIDE;
443  virtual void ShowForProfile(Profile* requested_profile) OVERRIDE;
444  virtual void DismissAppList() OVERRIDE;
445  virtual bool IsAppListVisible() const OVERRIDE;
446  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE;
447  virtual AppListControllerDelegate* CreateControllerDelegate() OVERRIDE;
448
449  // AppListServiceImpl overrides:
450  virtual void CreateShortcut() OVERRIDE;
451  virtual void OnSigninStatusChanged() OVERRIDE;
452
453 private:
454  friend struct DefaultSingletonTraits<AppListController>;
455
456  AppListController();
457
458  bool IsWarmupNeeded();
459  void ScheduleWarmup();
460
461  // Loads the profile last used with the app list and populates the view from
462  // it without showing it so that the next show is faster. Does nothing if the
463  // view already exists, or another profile is in the middle of being loaded to
464  // be shown.
465  void LoadProfileForWarmup();
466  void OnLoadProfileForWarmup(Profile* initial_profile);
467
468  // Creates an AppListView.
469  app_list::AppListView* CreateAppListView();
470
471  // Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up
472  // restart params).
473  void SetWindowAttributes(HWND hwnd);
474
475  // Utility methods for showing the app list.
476  gfx::Point FindAnchorPoint(const gfx::Display& display,
477                             const gfx::Point& cursor);
478  void UpdateArrowPositionAndAnchorPoint(const gfx::Point& cursor);
479  string16 GetAppListIconPath();
480
481  // Check if the app list or the taskbar has focus. The app list is kept
482  // visible whenever either of these have focus, which allows it to be
483  // pinned but will hide it if it otherwise loses focus. This is checked
484  // periodically whenever the app list does not have focus.
485  void CheckTaskbarOrViewHasFocus();
486
487  // Utilities to manage browser process keep alive for the view itself. Note
488  // keep alives are also used when asynchronously loading profiles.
489  void EnsureHaveKeepAliveForView();
490  void FreeAnyKeepAliveForView();
491
492  // Weak pointer. The view manages its own lifetime.
493  app_list::AppListView* current_view_;
494
495  scoped_ptr<ActivationTracker> activation_tracker_;
496
497  app_list::PaginationModel pagination_model_;
498
499  // True if the controller can close the app list.
500  bool can_close_app_list_;
501
502  // Used to keep the browser process alive while the app list is visible.
503  scoped_ptr<ScopedKeepAlive> keep_alive_;
504
505  bool enable_app_list_on_next_init_;
506
507  base::WeakPtrFactory<AppListController> weak_factory_;
508
509  DISALLOW_COPY_AND_ASSIGN(AppListController);
510};
511
512AppListControllerDelegateWin::AppListControllerDelegateWin() {}
513
514AppListControllerDelegateWin::~AppListControllerDelegateWin() {}
515
516void AppListControllerDelegateWin::DismissView() {
517  AppListController::GetInstance()->DismissAppList();
518}
519
520void AppListControllerDelegateWin::ViewActivationChanged(bool active) {
521  AppListController::GetInstance()->AppListActivationChanged(active);
522}
523
524void AppListControllerDelegateWin::ViewClosing() {
525  AppListController::GetInstance()->AppListClosing();
526}
527
528gfx::NativeWindow AppListControllerDelegateWin::GetAppListWindow() {
529  return AppListController::GetInstance()->GetAppListWindow();
530}
531
532gfx::ImageSkia AppListControllerDelegateWin::GetWindowIcon() {
533  gfx::ImageSkia* resource = ResourceBundle::GetSharedInstance().
534      GetImageSkiaNamed(chrome::GetAppListIconResourceId());
535  return *resource;
536}
537
538bool AppListControllerDelegateWin::CanPin() {
539  return false;
540}
541
542void AppListControllerDelegateWin::OnShowExtensionPrompt() {
543  AppListController::GetInstance()->set_can_close(false);
544}
545
546void AppListControllerDelegateWin::OnCloseExtensionPrompt() {
547  AppListController::GetInstance()->set_can_close(true);
548}
549
550bool AppListControllerDelegateWin::CanDoCreateShortcutsFlow(
551    bool is_platform_app) {
552  return true;
553}
554
555void AppListControllerDelegateWin::DoCreateShortcutsFlow(
556    Profile* profile,
557    const std::string& extension_id) {
558  ExtensionService* service =
559      extensions::ExtensionSystem::Get(profile)->extension_service();
560  DCHECK(service);
561  const extensions::Extension* extension = service->GetInstalledExtension(
562      extension_id);
563  DCHECK(extension);
564
565  app_list::AppListView* view = AppListController::GetInstance()->GetView();
566  if (!view)
567    return;
568
569  gfx::NativeWindow parent_hwnd =
570      view->GetWidget()->GetTopLevelWidget()->GetNativeWindow();
571  OnShowExtensionPrompt();
572  chrome::ShowCreateChromeAppShortcutsDialog(
573      parent_hwnd, profile, extension,
574      base::Bind(&AppListControllerDelegateWin::OnCloseExtensionPrompt,
575                 base::Unretained(this)));
576}
577
578void AppListControllerDelegateWin::CreateNewWindow(Profile* profile,
579                                                   bool incognito) {
580  Profile* window_profile = incognito ?
581      profile->GetOffTheRecordProfile() : profile;
582  chrome::NewEmptyWindow(window_profile, chrome::GetActiveDesktop());
583}
584
585void AppListControllerDelegateWin::ActivateApp(
586    Profile* profile, const extensions::Extension* extension, int event_flags) {
587  LaunchApp(profile, extension, event_flags);
588}
589
590void AppListControllerDelegateWin::LaunchApp(
591    Profile* profile, const extensions::Extension* extension, int event_flags) {
592  AppListServiceImpl::RecordAppListAppLaunch();
593  chrome::OpenApplication(chrome::AppLaunchParams(
594      profile, extension, NEW_FOREGROUND_TAB));
595}
596
597AppListController::AppListController()
598    : current_view_(NULL),
599      can_close_app_list_(true),
600      enable_app_list_on_next_init_(false),
601      weak_factory_(this) {}
602
603AppListController::~AppListController() {
604}
605
606gfx::NativeWindow AppListController::GetAppListWindow() {
607  if (!IsAppListVisible())
608    return NULL;
609  return current_view_ ? current_view_->GetWidget()->GetNativeWindow() : NULL;
610}
611
612AppListControllerDelegate* AppListController::CreateControllerDelegate() {
613  return new AppListControllerDelegateWin();
614}
615
616void AppListController::OnSigninStatusChanged() {
617  if (current_view_)
618    current_view_->OnSigninStatusChanged();
619}
620
621void AppListController::ShowForProfile(Profile* requested_profile) {
622  DCHECK(requested_profile);
623  if (requested_profile->IsManaged())
624    return;
625
626  ScopedKeepAlive show_app_list_keepalive;
627
628  content::BrowserThread::PostBlockingPoolTask(
629      FROM_HERE, base::Bind(SetDidRunForNDayActiveStats));
630
631  if (win8::IsSingleWindowMetroMode()) {
632    // This request came from Windows 8 in desktop mode, but chrome is currently
633    // running in Metro mode.
634    AppMetroInfoBarDelegateWin::Create(
635        requested_profile, AppMetroInfoBarDelegateWin::SHOW_APP_LIST,
636        std::string());
637    return;
638  }
639
640  InvalidatePendingProfileLoads();
641
642  // If the app list is already displaying |profile| just activate it (in case
643  // we have lost focus).
644  if (IsAppListVisible() && (requested_profile == profile())) {
645    current_view_->GetWidget()->Show();
646    current_view_->GetWidget()->Activate();
647    return;
648  }
649
650  SetProfilePath(requested_profile->GetPath());
651
652  DismissAppList();
653  CreateForProfile(requested_profile);
654
655  DCHECK(current_view_);
656  EnsureHaveKeepAliveForView();
657  gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
658  UpdateArrowPositionAndAnchorPoint(cursor);
659  current_view_->GetWidget()->Show();
660  current_view_->GetWidget()->GetTopLevelWidget()->UpdateWindowIcon();
661  current_view_->GetWidget()->Activate();
662  RecordAppListLaunch();
663}
664
665void AppListController::ShowAppListDuringModeSwitch(
666    Profile* requested_profile) {
667  ShowForProfile(requested_profile);
668  activation_tracker_->RegainNextLostFocus();
669}
670
671void AppListController::CreateForProfile(Profile* requested_profile) {
672  // Aura has problems with layered windows and bubble delegates. The app
673  // launcher has a trick where it only hides the window when it is dismissed,
674  // reshowing it again later. This does not work with win aura for some
675  // reason. This change temporarily makes it always get recreated, only on win
676  // aura. See http://crbug.com/176186.
677#if !defined(USE_AURA)
678  if (requested_profile == profile())
679    return;
680#endif
681
682  SetProfile(requested_profile);
683  current_view_ = CreateAppListView();
684  activation_tracker_.reset(new ActivationTracker(current_view_,
685      base::Bind(&AppListController::DismissAppList, base::Unretained(this))));
686}
687
688app_list::AppListView* AppListController::CreateAppListView() {
689  // The controller will be owned by the view delegate, and the delegate is
690  // owned by the app list view. The app list view manages it's own lifetime.
691  AppListViewDelegate* view_delegate =
692      new AppListViewDelegate(CreateControllerDelegate(), profile());
693  app_list::AppListView* view = new app_list::AppListView(view_delegate);
694  gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
695  view->InitAsBubble(NULL,
696                     &pagination_model_,
697                     NULL,
698                     cursor,
699                     views::BubbleBorder::FLOAT,
700                     false /* border_accepts_events */);
701  SetWindowAttributes(view->GetHWND());
702  return view;
703}
704
705void AppListController::SetWindowAttributes(HWND hwnd) {
706  // Vista and lower do not offer pinning to the taskbar, which makes any
707  // presence on the taskbar useless. So, hide the window on the taskbar
708  // for these versions of Windows.
709  if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
710    LONG_PTR ex_styles = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
711    ex_styles |= WS_EX_TOOLWINDOW;
712    SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex_styles);
713  }
714
715  if (base::win::GetVersion() > base::win::VERSION_VISTA) {
716    // Disable aero peek. Without this, hovering over the taskbar popup puts
717    // Windows into a mode for switching between windows in the same
718    // application. The app list has just one window, so it is just distracting.
719    BOOL disable_value = TRUE;
720    ::DwmSetWindowAttribute(hwnd,
721                            DWMWA_DISALLOW_PEEK,
722                            &disable_value,
723                            sizeof(disable_value));
724  }
725
726  ui::win::SetAppIdForWindow(GetAppModelId(), hwnd);
727  CommandLine relaunch = GetAppListCommandLine();
728  string16 app_name(GetAppListShortcutName());
729  ui::win::SetRelaunchDetailsForWindow(
730      relaunch.GetCommandLineString(), app_name, hwnd);
731  ::SetWindowText(hwnd, app_name.c_str());
732  string16 icon_path = GetAppListIconPath();
733  ui::win::SetAppIconForWindow(icon_path, hwnd);
734}
735
736void AppListController::DismissAppList() {
737  if (IsAppListVisible() && can_close_app_list_) {
738    current_view_->GetWidget()->Hide();
739    activation_tracker_->OnViewHidden();
740    FreeAnyKeepAliveForView();
741  }
742}
743
744void AppListController::AppListClosing() {
745  FreeAnyKeepAliveForView();
746  current_view_ = NULL;
747  activation_tracker_.reset();
748  SetProfile(NULL);
749}
750
751void AppListController::AppListActivationChanged(bool active) {
752  activation_tracker_->OnActivationChanged(active);
753}
754
755// Attempts to find the bounds of the Windows taskbar. Returns true on success.
756// |rect| is in screen coordinates. If the taskbar is in autohide mode and is
757// not visible, |rect| will be outside the current monitor's bounds, except for
758// one pixel of overlap where the edge of the taskbar is shown.
759bool GetTaskbarRect(gfx::Rect* rect) {
760  HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL);
761  if (!taskbar_hwnd)
762    return false;
763
764  RECT win_rect;
765  if (!GetWindowRect(taskbar_hwnd, &win_rect))
766    return false;
767
768  *rect = gfx::Rect(win_rect);
769  return true;
770}
771
772gfx::Point FindReferencePoint(const gfx::Display& display,
773                              const gfx::Point& cursor) {
774  const int kSnapDistance = 50;
775
776  // If we can't find the taskbar, snap to the bottom left.
777  // If the display size is the same as the work area, and does not contain the
778  // taskbar, either the taskbar is hidden or on another monitor, so just snap
779  // to the bottom left.
780  gfx::Rect taskbar_rect;
781  if (!GetTaskbarRect(&taskbar_rect) ||
782      (display.work_area() == display.bounds() &&
783          !display.work_area().Contains(taskbar_rect))) {
784    return display.work_area().bottom_left();
785  }
786
787  // Snap to the taskbar edge. If the cursor is greater than kSnapDistance away,
788  // also move to the left (for horizontal taskbars) or top (for vertical).
789  const gfx::Rect& screen_rect = display.bounds();
790  // First handle taskbar on bottom.
791  if (taskbar_rect.width() == screen_rect.width()) {
792    if (taskbar_rect.bottom() == screen_rect.bottom()) {
793      if (taskbar_rect.y() - cursor.y() > kSnapDistance)
794        return gfx::Point(screen_rect.x(), taskbar_rect.y());
795
796      return gfx::Point(cursor.x(), taskbar_rect.y());
797    }
798
799    // Now try on the top.
800    if (cursor.y() - taskbar_rect.bottom() > kSnapDistance)
801      return gfx::Point(screen_rect.x(), taskbar_rect.bottom());
802
803    return gfx::Point(cursor.x(), taskbar_rect.bottom());
804  }
805
806  // Now try the left.
807  if (taskbar_rect.x() == screen_rect.x()) {
808    if (cursor.x() - taskbar_rect.right() > kSnapDistance)
809      return gfx::Point(taskbar_rect.right(), screen_rect.y());
810
811    return gfx::Point(taskbar_rect.right(), cursor.y());
812  }
813
814  // Finally, try the right.
815  if (taskbar_rect.x() - cursor.x() > kSnapDistance)
816    return gfx::Point(taskbar_rect.x(), screen_rect.y());
817
818  return gfx::Point(taskbar_rect.x(), cursor.y());
819}
820
821gfx::Point AppListController::FindAnchorPoint(
822    const gfx::Display& display,
823    const gfx::Point& cursor) {
824  const int kSnapOffset = 3;
825
826  gfx::Rect bounds_rect(display.work_area());
827  // Always subtract the taskbar area since work_area() will not subtract it if
828  // the taskbar is set to auto-hide, and the app list should never overlap the
829  // taskbar.
830  gfx::Rect taskbar_rect;
831  if (GetTaskbarRect(&taskbar_rect))
832    bounds_rect.Subtract(taskbar_rect);
833
834  gfx::Size view_size(current_view_->GetPreferredSize());
835  bounds_rect.Inset(view_size.width() / 2 + kSnapOffset,
836                    view_size.height() / 2 + kSnapOffset);
837
838  gfx::Point anchor = FindReferencePoint(display, cursor);
839  anchor.SetToMax(bounds_rect.origin());
840  anchor.SetToMin(bounds_rect.bottom_right());
841  return anchor;
842}
843
844void AppListController::UpdateArrowPositionAndAnchorPoint(
845    const gfx::Point& cursor) {
846  gfx::Screen* screen =
847      gfx::Screen::GetScreenFor(current_view_->GetWidget()->GetNativeView());
848  gfx::Display display = screen->GetDisplayNearestPoint(cursor);
849
850  current_view_->SetBubbleArrow(views::BubbleBorder::FLOAT);
851  current_view_->SetAnchorPoint(FindAnchorPoint(display, cursor));
852}
853
854string16 AppListController::GetAppListIconPath() {
855  base::FilePath icon_path;
856  if (!PathService::Get(base::FILE_EXE, &icon_path)) {
857    NOTREACHED();
858    return string16();
859  }
860
861  std::stringstream ss;
862  ss << "," << GetAppListIconIndex();
863  string16 result = icon_path.value();
864  result.append(UTF8ToUTF16(ss.str()));
865  return result;
866}
867
868void AppListController::EnsureHaveKeepAliveForView() {
869  if (!keep_alive_)
870    keep_alive_.reset(new ScopedKeepAlive());
871}
872
873void AppListController::FreeAnyKeepAliveForView() {
874  if (keep_alive_)
875    keep_alive_.reset(NULL);
876}
877
878void AppListController::OnLoadProfileForWarmup(Profile* initial_profile) {
879  if (!IsWarmupNeeded())
880    return;
881
882  CreateForProfile(initial_profile);
883  current_view_->Prerender();
884}
885
886void AppListController::HandleFirstRun() {
887  PrefService* local_state = g_browser_process->local_state();
888  // If the app list is already enabled during first run, then the user had
889  // opted in to the app launcher before uninstalling, so we re-enable to
890  // restore shortcuts to the app list.
891  // Note we can't directly create the shortcuts here because the IO thread
892  // hasn't been created yet.
893  enable_app_list_on_next_init_ = local_state->GetBoolean(
894      apps::prefs::kAppLauncherHasBeenEnabled);
895}
896
897void AppListController::Init(Profile* initial_profile) {
898  // In non-Ash metro mode, we can not show the app list for this process, so do
899  // not bother performing Init tasks.
900  if (win8::IsSingleWindowMetroMode())
901    return;
902
903  if (enable_app_list_on_next_init_) {
904    enable_app_list_on_next_init_ = false;
905    EnableAppList(initial_profile);
906    CreateShortcut();
907  }
908
909  PrefService* prefs = g_browser_process->local_state();
910  if (prefs->HasPrefPath(prefs::kRestartWithAppList) &&
911      prefs->GetBoolean(prefs::kRestartWithAppList)) {
912    prefs->SetBoolean(prefs::kRestartWithAppList, false);
913    AppListController::GetInstance()->
914        ShowAppListDuringModeSwitch(initial_profile);
915  }
916
917  // Migrate from legacy app launcher if we are on a non-canary and non-chromium
918  // build.
919#if defined(GOOGLE_CHROME_BUILD)
920  if (!InstallUtil::IsChromeSxSProcess() &&
921      !chrome_launcher_support::GetAnyAppHostPath().empty()) {
922    chrome_launcher_support::InstallationState state =
923        chrome_launcher_support::GetAppLauncherInstallationState();
924    if (state == chrome_launcher_support::NOT_INSTALLED) {
925      // If app_host.exe is found but can't be located in the registry,
926      // skip the migration as this is likely a developer build.
927      return;
928    } else if (state == chrome_launcher_support::INSTALLED_AT_SYSTEM_LEVEL) {
929      chrome_launcher_support::UninstallLegacyAppLauncher(
930          chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION);
931    } else if (state == chrome_launcher_support::INSTALLED_AT_USER_LEVEL) {
932      chrome_launcher_support::UninstallLegacyAppLauncher(
933          chrome_launcher_support::USER_LEVEL_INSTALLATION);
934    }
935    EnableAppList(initial_profile);
936    CreateShortcut();
937  }
938#endif
939
940  // Instantiate AppListController so it listens for profile deletions.
941  AppListController::GetInstance();
942
943  ScheduleWarmup();
944
945  MigrateAppLauncherEnabledPref();
946  HandleCommandLineFlags(initial_profile);
947}
948
949bool AppListController::IsAppListVisible() const {
950  return current_view_ && current_view_->GetWidget()->IsVisible();
951}
952
953void AppListController::CreateShortcut() {
954  // Check if the app launcher shortcuts have ever been created before.
955  // Shortcuts should only be created once. If the user unpins the taskbar
956  // shortcut, they can restore it by pinning the start menu or desktop
957  // shortcut.
958  ShellIntegration::ShortcutLocations shortcut_locations;
959  shortcut_locations.on_desktop = true;
960  shortcut_locations.in_quick_launch_bar = true;
961  shortcut_locations.in_applications_menu = true;
962  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
963  shortcut_locations.applications_menu_subdir = dist->GetAppShortCutName();
964  base::FilePath user_data_dir(
965      g_browser_process->profile_manager()->user_data_dir());
966
967  content::BrowserThread::PostTask(
968      content::BrowserThread::FILE,
969      FROM_HERE,
970      base::Bind(&CreateAppListShortcuts,
971                  user_data_dir, GetAppModelId(), shortcut_locations));
972}
973
974void AppListController::ScheduleWarmup() {
975  // Post a task to create the app list. This is posted to not impact startup
976  // time.
977  const int kInitWindowDelay = 5;
978  base::MessageLoop::current()->PostDelayedTask(
979      FROM_HERE,
980      base::Bind(&AppListController::LoadProfileForWarmup,
981                 weak_factory_.GetWeakPtr()),
982      base::TimeDelta::FromSeconds(kInitWindowDelay));
983
984  // Send app list usage stats after a delay.
985  const int kSendUsageStatsDelay = 5;
986  base::MessageLoop::current()->PostDelayedTask(
987      FROM_HERE,
988      base::Bind(&AppListController::SendAppListStats),
989      base::TimeDelta::FromSeconds(kSendUsageStatsDelay));
990}
991
992bool AppListController::IsWarmupNeeded() {
993  if (!g_browser_process || g_browser_process->IsShuttingDown())
994    return false;
995
996  // We only need to initialize the view if there's no view already created and
997  // there's no profile loading to be shown.
998  return !current_view_ && !profile_loader().IsAnyProfileLoading();
999}
1000
1001void AppListController::LoadProfileForWarmup() {
1002  if (!IsWarmupNeeded())
1003    return;
1004
1005  ProfileManager* profile_manager = g_browser_process->profile_manager();
1006  base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir()));
1007
1008  profile_loader().LoadProfileInvalidatingOtherLoads(
1009      profile_path,
1010      base::Bind(&AppListController::OnLoadProfileForWarmup,
1011                 weak_factory_.GetWeakPtr()));
1012}
1013
1014}  // namespace
1015
1016namespace chrome {
1017
1018AppListService* GetAppListServiceWin() {
1019  return AppListController::GetInstance();
1020}
1021
1022}  // namespace chrome
1023