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