app_list_service_win.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright 2013 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/views/app_list/win/app_list_service_win.h"
6
7#include <dwmapi.h>
8#include <sstream>
9
10#include "base/command_line.h"
11#include "base/file_util.h"
12#include "base/lazy_instance.h"
13#include "base/memory/weak_ptr.h"
14#include "base/message_loop/message_loop.h"
15#include "base/metrics/histogram.h"
16#include "base/path_service.h"
17#include "base/prefs/pref_service.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/threading/sequenced_worker_pool.h"
20#include "base/time/time.h"
21#include "base/timer/timer.h"
22#include "base/win/shortcut.h"
23#include "base/win/windows_version.h"
24#include "chrome/app/chrome_dll_resource.h"
25#include "chrome/browser/browser_process.h"
26#include "chrome/browser/platform_util.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/profiles/profile_manager.h"
29#include "chrome/browser/shell_integration.h"
30#include "chrome/browser/ui/app_list/app_list.h"
31#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
32#include "chrome/browser/ui/app_list/app_list_factory.h"
33#include "chrome/browser/ui/app_list/app_list_service_impl.h"
34#include "chrome/browser/ui/app_list/app_list_shower.h"
35#include "chrome/browser/ui/app_list/app_list_view_delegate.h"
36#include "chrome/browser/ui/app_list/keep_alive_service_impl.h"
37#include "chrome/browser/ui/apps/app_metro_infobar_delegate_win.h"
38#include "chrome/browser/ui/views/app_list/win/activation_tracker_win.h"
39#include "chrome/browser/ui/views/app_list/win/app_list_controller_delegate_win.h"
40#include "chrome/browser/ui/views/app_list/win/app_list_win.h"
41#include "chrome/browser/web_applications/web_app.h"
42#include "chrome/common/chrome_constants.h"
43#include "chrome/common/chrome_switches.h"
44#include "chrome/common/chrome_version_info.h"
45#include "chrome/common/pref_names.h"
46#include "chrome/installer/launcher_support/chrome_launcher_support.h"
47#include "chrome/installer/util/browser_distribution.h"
48#include "chrome/installer/util/google_update_settings.h"
49#include "chrome/installer/util/install_util.h"
50#include "chrome/installer/util/util_constants.h"
51#include "content/public/browser/browser_thread.h"
52#include "grit/chromium_strings.h"
53#include "grit/generated_resources.h"
54#include "grit/google_chrome_strings.h"
55#include "grit/theme_resources.h"
56#include "ui/app_list/app_list_model.h"
57#include "ui/app_list/pagination_model.h"
58#include "ui/app_list/views/app_list_view.h"
59#include "ui/base/l10n/l10n_util.h"
60#include "ui/base/resource/resource_bundle.h"
61#include "ui/base/win/shell.h"
62#include "ui/gfx/display.h"
63#include "ui/gfx/image/image_skia.h"
64#include "ui/gfx/screen.h"
65#include "ui/views/bubble/bubble_border.h"
66#include "ui/views/widget/widget.h"
67#include "win8/util/win8_util.h"
68
69#if defined(USE_AURA)
70#include "ui/aura/root_window.h"
71#include "ui/aura/window.h"
72#endif
73
74#if defined(USE_ASH)
75#include "chrome/browser/ui/app_list/app_list_service_ash.h"
76#include "chrome/browser/ui/host_desktop.h"
77#endif
78
79#if defined(GOOGLE_CHROME_BUILD)
80#include "chrome/installer/util/install_util.h"
81#endif
82
83// static
84AppListService* AppListService::Get() {
85#if defined(USE_ASH)
86  if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH)
87    return chrome::GetAppListServiceAsh();
88#endif
89
90  return chrome::GetAppListServiceWin();
91}
92
93// static
94void AppListService::InitAll(Profile* initial_profile) {
95#if defined(USE_ASH)
96  chrome::GetAppListServiceAsh()->Init(initial_profile);
97#endif
98  chrome::GetAppListServiceWin()->Init(initial_profile);
99}
100
101namespace {
102
103// Migrate chrome::kAppLauncherIsEnabled pref to
104// chrome::kAppLauncherHasBeenEnabled pref.
105void MigrateAppLauncherEnabledPref() {
106  PrefService* prefs = g_browser_process->local_state();
107  if (prefs->HasPrefPath(prefs::kAppLauncherIsEnabled)) {
108    prefs->SetBoolean(prefs::kAppLauncherHasBeenEnabled,
109                      prefs->GetBoolean(prefs::kAppLauncherIsEnabled));
110    prefs->ClearPref(prefs::kAppLauncherIsEnabled);
111  }
112}
113
114// Icons are added to the resources of the DLL using icon names. The icon index
115// for the app list icon is named IDR_X_APP_LIST or (for official builds)
116// IDR_X_APP_LIST_SXS for Chrome Canary. Creating shortcuts needs to specify a
117// resource index, which are different to icon names.  They are 0 based and
118// contiguous. As Google Chrome builds have extra icons the icon for Google
119// Chrome builds need to be higher. Unfortunately these indexes are not in any
120// generated header file.
121int GetAppListIconIndex() {
122  const int kAppListIconIndex = 5;
123  const int kAppListIconIndexSxS = 6;
124  const int kAppListIconIndexChromium = 1;
125#if defined(GOOGLE_CHROME_BUILD)
126  if (InstallUtil::IsChromeSxSProcess())
127    return kAppListIconIndexSxS;
128  return kAppListIconIndex;
129#else
130  return kAppListIconIndexChromium;
131#endif
132}
133
134string16 GetAppListIconPath() {
135  base::FilePath icon_path;
136  if (!PathService::Get(base::FILE_EXE, &icon_path)) {
137    NOTREACHED();
138    return string16();
139  }
140
141  std::stringstream ss;
142  ss << "," << GetAppListIconIndex();
143  string16 result = icon_path.value();
144  result.append(UTF8ToUTF16(ss.str()));
145  return result;
146}
147
148string16 GetAppListShortcutName() {
149  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
150  if (channel == chrome::VersionInfo::CHANNEL_CANARY)
151    return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY);
152  return l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME);
153}
154
155CommandLine GetAppListCommandLine() {
156  const char* const kSwitchesToCopy[] = { switches::kUserDataDir };
157  CommandLine* current = CommandLine::ForCurrentProcess();
158  base::FilePath chrome_exe;
159  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
160     NOTREACHED();
161     return CommandLine(CommandLine::NO_PROGRAM);
162  }
163  CommandLine command_line(chrome_exe);
164  command_line.CopySwitchesFrom(*current, kSwitchesToCopy,
165                                arraysize(kSwitchesToCopy));
166  command_line.AppendSwitch(switches::kShowAppList);
167  return command_line;
168}
169
170string16 GetAppModelId() {
171  // The AppModelId should be the same for all profiles in a user data directory
172  // but different for different user data directories, so base it on the
173  // initial profile in the current user data directory.
174  base::FilePath initial_profile_path;
175  CommandLine* command_line = CommandLine::ForCurrentProcess();
176  if (command_line->HasSwitch(switches::kUserDataDir)) {
177    initial_profile_path =
178        command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII(
179            chrome::kInitialProfile);
180  }
181  return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path);
182}
183
184void SetDidRunForNDayActiveStats() {
185  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
186  base::FilePath exe_path;
187  if (!PathService::Get(base::DIR_EXE, &exe_path)) {
188    NOTREACHED();
189    return;
190  }
191  bool system_install =
192      !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
193  // Using Chrome Binary dist: Chrome dist may not exist for the legacy
194  // App Launcher, and App Launcher dist may be "shadow", which does not
195  // contain the information needed to determine multi-install.
196  // Edge case involving Canary: crbug/239163.
197  BrowserDistribution* chrome_binaries_dist =
198      BrowserDistribution::GetSpecificDistribution(
199          BrowserDistribution::CHROME_BINARIES);
200  if (chrome_binaries_dist &&
201      InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) {
202    BrowserDistribution* app_launcher_dist =
203        BrowserDistribution::GetSpecificDistribution(
204            BrowserDistribution::CHROME_APP_HOST);
205    GoogleUpdateSettings::UpdateDidRunStateForDistribution(
206        app_launcher_dist,
207        true /* did_run */,
208        system_install);
209  }
210}
211
212// The start menu shortcut is created on first run by users that are
213// upgrading. The desktop and taskbar shortcuts are created the first time the
214// user enables the app list. The taskbar shortcut is created in
215// |user_data_dir| and will use a Windows Application Model Id of
216// |app_model_id|. This runs on the FILE thread and not in the blocking IO
217// thread pool as there are other tasks running (also on the FILE thread)
218// which fiddle with shortcut icons
219// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads
220// fiddle with the same shortcuts could cause race issues.
221void CreateAppListShortcuts(
222    const base::FilePath& user_data_dir,
223    const string16& app_model_id,
224    const ShellIntegration::ShortcutLocations& creation_locations) {
225  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
226
227  // Shortcut paths under which to create shortcuts.
228  std::vector<base::FilePath> shortcut_paths =
229      web_app::internals::GetShortcutPaths(creation_locations);
230
231  bool pin_to_taskbar = creation_locations.in_quick_launch_bar &&
232                        (base::win::GetVersion() >= base::win::VERSION_WIN7);
233
234  // Create a shortcut in the |user_data_dir| for taskbar pinning.
235  if (pin_to_taskbar)
236    shortcut_paths.push_back(user_data_dir);
237  bool success = true;
238
239  base::FilePath chrome_exe;
240  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
241    NOTREACHED();
242    return;
243  }
244
245  string16 app_list_shortcut_name = GetAppListShortcutName();
246
247  string16 wide_switches(GetAppListCommandLine().GetArgumentsString());
248
249  base::win::ShortcutProperties shortcut_properties;
250  shortcut_properties.set_target(chrome_exe);
251  shortcut_properties.set_working_dir(chrome_exe.DirName());
252  shortcut_properties.set_arguments(wide_switches);
253  shortcut_properties.set_description(app_list_shortcut_name);
254  shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex());
255  shortcut_properties.set_app_id(app_model_id);
256
257  for (size_t i = 0; i < shortcut_paths.size(); ++i) {
258    base::FilePath shortcut_file =
259        shortcut_paths[i].Append(app_list_shortcut_name).
260            AddExtension(installer::kLnkExt);
261    if (!base::PathExists(shortcut_file.DirName()) &&
262        !file_util::CreateDirectory(shortcut_file.DirName())) {
263      NOTREACHED();
264      return;
265    }
266    success = success && base::win::CreateOrUpdateShortcutLink(
267        shortcut_file, shortcut_properties,
268        base::win::SHORTCUT_CREATE_ALWAYS);
269  }
270
271  if (success && pin_to_taskbar) {
272    base::FilePath shortcut_to_pin =
273        user_data_dir.Append(app_list_shortcut_name).
274            AddExtension(installer::kLnkExt);
275    success = base::win::TaskbarPinShortcutLink(
276        shortcut_to_pin.value().c_str()) && success;
277  }
278}
279
280// Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up
281// restart params).
282void SetWindowAttributes(HWND hwnd) {
283  // Vista and lower do not offer pinning to the taskbar, which makes any
284  // presence on the taskbar useless. So, hide the window on the taskbar
285  // for these versions of Windows.
286  if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
287    LONG_PTR ex_styles = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
288    ex_styles |= WS_EX_TOOLWINDOW;
289    SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex_styles);
290  }
291
292  if (base::win::GetVersion() > base::win::VERSION_VISTA) {
293    // Disable aero peek. Without this, hovering over the taskbar popup puts
294    // Windows into a mode for switching between windows in the same
295    // application. The app list has just one window, so it is just distracting.
296    BOOL disable_value = TRUE;
297    ::DwmSetWindowAttribute(hwnd,
298                            DWMWA_DISALLOW_PEEK,
299                            &disable_value,
300                            sizeof(disable_value));
301  }
302
303  ui::win::SetAppIdForWindow(GetAppModelId(), hwnd);
304  CommandLine relaunch = GetAppListCommandLine();
305  string16 app_name(GetAppListShortcutName());
306  ui::win::SetRelaunchDetailsForWindow(
307      relaunch.GetCommandLineString(), app_name, hwnd);
308  ::SetWindowText(hwnd, app_name.c_str());
309  string16 icon_path = GetAppListIconPath();
310  ui::win::SetAppIconForWindow(icon_path, hwnd);
311}
312
313class AppListFactoryWin : public AppListFactory {
314 public:
315  explicit AppListFactoryWin(
316      scoped_ptr<AppListControllerDelegate> delegate)
317      : delegate_(delegate.Pass()) {}
318  virtual ~AppListFactoryWin() {}
319
320  virtual AppList* CreateAppList(
321      Profile* profile,
322      const base::Closure& on_should_dismiss) OVERRIDE {
323    // The controller will be owned by the view delegate, and the delegate is
324    // owned by the app list view. The app list view manages it's own lifetime.
325    // TODO(koz): Make AppListViewDelegate take a scoped_ptr.
326    AppListViewDelegate* view_delegate = new AppListViewDelegate(
327        delegate_.get(), profile);
328    app_list::AppListView* view = new app_list::AppListView(view_delegate);
329    gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
330    view->InitAsBubbleAtFixedLocation(NULL,
331                                      &pagination_model_,
332                                      cursor,
333                                      views::BubbleBorder::FLOAT,
334                                      false /* border_accepts_events */);
335    SetWindowAttributes(view->GetHWND());
336    return new AppListWin(view, on_should_dismiss);
337  }
338
339 private:
340  // PaginationModel that is shared across all views.
341  app_list::PaginationModel pagination_model_;
342
343  scoped_ptr<AppListControllerDelegate> delegate_;
344  DISALLOW_COPY_AND_ASSIGN(AppListFactoryWin);
345};
346
347}  // namespace
348
349// static
350AppListServiceWin* AppListServiceWin::GetInstance() {
351  return Singleton<AppListServiceWin,
352                   LeakySingletonTraits<AppListServiceWin> >::get();
353}
354
355AppListServiceWin::AppListServiceWin()
356    : enable_app_list_on_next_init_(false),
357      shower_(new AppListShower(
358          scoped_ptr<AppListFactory>(
359              new AppListFactoryWin(scoped_ptr<AppListControllerDelegate>(
360                  new AppListControllerDelegateWin(this)))),
361          scoped_ptr<KeepAliveService>(new KeepAliveServiceImpl))),
362      weak_factory_(this) {}
363
364AppListServiceWin::~AppListServiceWin() {
365}
366
367void AppListServiceWin::set_can_close(bool can_close) {
368  shower_->set_can_close(can_close);
369}
370
371gfx::NativeWindow AppListServiceWin::GetAppListWindow() {
372  return shower_->GetWindow();
373}
374
375AppListControllerDelegate* AppListServiceWin::CreateControllerDelegate() {
376  return new AppListControllerDelegateWin(this);
377}
378
379app_list::AppListModel* AppListServiceWin::GetAppListModelForTesting() {
380  return static_cast<AppListWin*>(shower_->app_list())->model();
381}
382
383void AppListServiceWin::ShowForProfile(Profile* requested_profile) {
384  DCHECK(requested_profile);
385  if (requested_profile->IsManaged())
386    return;
387
388  ScopedKeepAlive show_app_list_keepalive;
389
390  content::BrowserThread::PostBlockingPoolTask(
391      FROM_HERE, base::Bind(SetDidRunForNDayActiveStats));
392
393  if (win8::IsSingleWindowMetroMode()) {
394    // This request came from Windows 8 in desktop mode, but chrome is currently
395    // running in Metro mode.
396    AppMetroInfoBarDelegateWin::Create(
397        requested_profile, AppMetroInfoBarDelegateWin::SHOW_APP_LIST,
398        std::string());
399    return;
400  }
401
402  InvalidatePendingProfileLoads();
403  // TODO(koz): Investigate making SetProfile() call SetProfilePath() itself.
404  SetProfilePath(requested_profile->GetPath());
405  SetProfile(requested_profile);
406  shower_->ShowForProfile(requested_profile);
407  RecordAppListLaunch();
408}
409
410void AppListServiceWin::DismissAppList() {
411  shower_->DismissAppList();
412}
413
414void AppListServiceWin::OnAppListClosing() {
415  shower_->CloseAppList();
416  SetProfile(NULL);
417}
418
419void AppListServiceWin::OnLoadProfileForWarmup(Profile* initial_profile) {
420  if (!IsWarmupNeeded())
421    return;
422
423  base::Time before_warmup(base::Time::Now());
424  shower_->WarmupForProfile(initial_profile);
425  UMA_HISTOGRAM_TIMES("Apps.AppListWarmupDuration",
426                      base::Time::Now() - before_warmup);
427}
428
429void AppListServiceWin::SetAppListNextPaintCallback(
430    const base::Closure& callback) {
431  app_list::AppListView::SetNextPaintCallback(callback);
432}
433
434void AppListServiceWin::HandleFirstRun() {
435  PrefService* local_state = g_browser_process->local_state();
436  // If the app list is already enabled during first run, then the user had
437  // opted in to the app launcher before uninstalling, so we re-enable to
438  // restore shortcuts to the app list.
439  // Note we can't directly create the shortcuts here because the IO thread
440  // hasn't been created yet.
441  enable_app_list_on_next_init_ = local_state->GetBoolean(
442      prefs::kAppLauncherHasBeenEnabled);
443}
444
445void AppListServiceWin::Init(Profile* initial_profile) {
446  // In non-Ash metro mode, we can not show the app list for this process, so do
447  // not bother performing Init tasks.
448  if (win8::IsSingleWindowMetroMode())
449    return;
450
451  if (enable_app_list_on_next_init_) {
452    enable_app_list_on_next_init_ = false;
453    EnableAppList(initial_profile);
454    CreateShortcut();
455  }
456
457  PrefService* prefs = g_browser_process->local_state();
458  if (prefs->HasPrefPath(prefs::kRestartWithAppList) &&
459      prefs->GetBoolean(prefs::kRestartWithAppList)) {
460    prefs->SetBoolean(prefs::kRestartWithAppList, false);
461    // If we are restarting in Metro mode we will lose focus straight away. We
462    // need to reacquire focus when that happens.
463    shower_->ShowAndReacquireFocus(initial_profile);
464  }
465
466  // Migrate from legacy app launcher if we are on a non-canary and non-chromium
467  // build.
468#if defined(GOOGLE_CHROME_BUILD)
469  if (!InstallUtil::IsChromeSxSProcess() &&
470      !chrome_launcher_support::GetAnyAppHostPath().empty()) {
471    chrome_launcher_support::InstallationState state =
472        chrome_launcher_support::GetAppLauncherInstallationState();
473    if (state == chrome_launcher_support::NOT_INSTALLED) {
474      // If app_host.exe is found but can't be located in the registry,
475      // skip the migration as this is likely a developer build.
476      return;
477    } else if (state == chrome_launcher_support::INSTALLED_AT_SYSTEM_LEVEL) {
478      chrome_launcher_support::UninstallLegacyAppLauncher(
479          chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION);
480    } else if (state == chrome_launcher_support::INSTALLED_AT_USER_LEVEL) {
481      chrome_launcher_support::UninstallLegacyAppLauncher(
482          chrome_launcher_support::USER_LEVEL_INSTALLATION);
483    }
484    EnableAppList(initial_profile);
485    CreateShortcut();
486  }
487#endif
488
489  ScheduleWarmup();
490
491  MigrateAppLauncherEnabledPref();
492  HandleCommandLineFlags(initial_profile);
493  SendUsageStats();
494}
495
496void AppListServiceWin::CreateForProfile(Profile* profile) {
497  shower_->CreateViewForProfile(profile);
498}
499
500bool AppListServiceWin::IsAppListVisible() const {
501  return shower_->IsAppListVisible();
502}
503
504void AppListServiceWin::CreateShortcut() {
505  // Check if the app launcher shortcuts have ever been created before.
506  // Shortcuts should only be created once. If the user unpins the taskbar
507  // shortcut, they can restore it by pinning the start menu or desktop
508  // shortcut.
509  ShellIntegration::ShortcutLocations shortcut_locations;
510  shortcut_locations.on_desktop = true;
511  shortcut_locations.in_quick_launch_bar = true;
512  shortcut_locations.in_applications_menu = true;
513  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
514  shortcut_locations.applications_menu_subdir =
515      dist->GetStartMenuShortcutSubfolder(
516          BrowserDistribution::SUBFOLDER_CHROME);
517  base::FilePath user_data_dir(
518      g_browser_process->profile_manager()->user_data_dir());
519
520  content::BrowserThread::PostTask(
521      content::BrowserThread::FILE,
522      FROM_HERE,
523      base::Bind(&CreateAppListShortcuts,
524                 user_data_dir, GetAppModelId(), shortcut_locations));
525}
526
527void AppListServiceWin::ScheduleWarmup() {
528  // Post a task to create the app list. This is posted to not impact startup
529  // time.
530  const int kInitWindowDelay = 30;
531  base::MessageLoop::current()->PostDelayedTask(
532      FROM_HERE,
533      base::Bind(&AppListServiceWin::LoadProfileForWarmup,
534                 weak_factory_.GetWeakPtr()),
535      base::TimeDelta::FromSeconds(kInitWindowDelay));
536}
537
538bool AppListServiceWin::IsWarmupNeeded() {
539  if (!g_browser_process || g_browser_process->IsShuttingDown())
540    return false;
541
542  // We only need to initialize the view if there's no view already created and
543  // there's no profile loading to be shown.
544  return !shower_->HasView() && !profile_loader().IsAnyProfileLoading();
545}
546
547void AppListServiceWin::LoadProfileForWarmup() {
548  if (!IsWarmupNeeded())
549    return;
550
551  ProfileManager* profile_manager = g_browser_process->profile_manager();
552  base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir()));
553
554  profile_loader().LoadProfileInvalidatingOtherLoads(
555      profile_path,
556      base::Bind(&AppListServiceWin::OnLoadProfileForWarmup,
557                 weak_factory_.GetWeakPtr()));
558}
559
560namespace chrome {
561
562AppListService* GetAppListServiceWin() {
563  return AppListServiceWin::GetInstance();
564}
565
566}  // namespace chrome
567