app_list_service_win.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/files/file_util.h"
12#include "base/memory/singleton.h"
13#include "base/message_loop/message_loop.h"
14#include "base/metrics/histogram.h"
15#include "base/path_service.h"
16#include "base/prefs/pref_service.h"
17#include "base/strings/utf_string_conversions.h"
18#include "base/threading/sequenced_worker_pool.h"
19#include "base/time/time.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/browser_shutdown.h"
25#include "chrome/browser/platform_util.h"
26#include "chrome/browser/profiles/profile.h"
27#include "chrome/browser/profiles/profile_manager.h"
28#include "chrome/browser/shell_integration.h"
29#include "chrome/browser/ui/ash/app_list/app_list_service_ash.h"
30#include "chrome/browser/ui/views/app_list/win/activation_tracker_win.h"
31#include "chrome/browser/ui/views/app_list/win/app_list_controller_delegate_win.h"
32#include "chrome/browser/ui/views/app_list/win/app_list_win.h"
33#include "chrome/browser/web_applications/web_app.h"
34#include "chrome/common/chrome_constants.h"
35#include "chrome/common/chrome_switches.h"
36#include "chrome/common/chrome_version_info.h"
37#include "chrome/common/pref_names.h"
38#include "chrome/installer/util/browser_distribution.h"
39#include "content/public/browser/browser_thread.h"
40#include "ui/app_list/views/app_list_view.h"
41#include "ui/base/win/shell.h"
42
43#if defined(GOOGLE_CHROME_BUILD)
44#include "chrome/installer/util/google_update_settings.h"
45#include "chrome/installer/util/install_util.h"
46#include "chrome/installer/util/updating_app_registration_data.h"
47#include "chrome/installer/util/util_constants.h"
48#endif  // GOOGLE_CHROME_BUILD
49
50// static
51AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) {
52  if (desktop_type == chrome::HOST_DESKTOP_TYPE_ASH)
53    return AppListServiceAsh::GetInstance();
54
55  return AppListServiceWin::GetInstance();
56}
57
58// static
59void AppListService::InitAll(Profile* initial_profile) {
60  AppListServiceAsh::GetInstance()->Init(initial_profile);
61  AppListServiceWin::GetInstance()->Init(initial_profile);
62}
63
64namespace {
65
66// Migrate chrome::kAppLauncherIsEnabled pref to
67// chrome::kAppLauncherHasBeenEnabled pref.
68void MigrateAppLauncherEnabledPref() {
69  PrefService* prefs = g_browser_process->local_state();
70  if (prefs->HasPrefPath(prefs::kAppLauncherIsEnabled)) {
71    prefs->SetBoolean(prefs::kAppLauncherHasBeenEnabled,
72                      prefs->GetBoolean(prefs::kAppLauncherIsEnabled));
73    prefs->ClearPref(prefs::kAppLauncherIsEnabled);
74  }
75}
76
77int GetAppListIconIndex() {
78  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
79  return dist->GetIconIndex(BrowserDistribution::SHORTCUT_APP_LAUNCHER);
80}
81
82base::string16 GetAppListIconPath() {
83  base::FilePath icon_path;
84  if (!PathService::Get(base::FILE_EXE, &icon_path)) {
85    NOTREACHED();
86    return base::string16();
87  }
88
89  std::stringstream ss;
90  ss << "," << GetAppListIconIndex();
91  base::string16 result = icon_path.value();
92  result.append(base::UTF8ToUTF16(ss.str()));
93  return result;
94}
95
96base::string16 GetAppListShortcutName() {
97  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
98  return dist->GetShortcutName(BrowserDistribution::SHORTCUT_APP_LAUNCHER);
99}
100
101CommandLine GetAppListCommandLine() {
102  const char* const kSwitchesToCopy[] = { switches::kUserDataDir };
103  CommandLine* current = CommandLine::ForCurrentProcess();
104  base::FilePath chrome_exe;
105  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
106     NOTREACHED();
107     return CommandLine(CommandLine::NO_PROGRAM);
108  }
109  CommandLine command_line(chrome_exe);
110  command_line.CopySwitchesFrom(*current, kSwitchesToCopy,
111                                arraysize(kSwitchesToCopy));
112  command_line.AppendSwitch(switches::kShowAppList);
113  return command_line;
114}
115
116base::string16 GetAppModelId() {
117  // The AppModelId should be the same for all profiles in a user data directory
118  // but different for different user data directories, so base it on the
119  // initial profile in the current user data directory.
120  base::FilePath initial_profile_path;
121  CommandLine* command_line = CommandLine::ForCurrentProcess();
122  if (command_line->HasSwitch(switches::kUserDataDir)) {
123    initial_profile_path =
124        command_line->GetSwitchValuePath(switches::kUserDataDir).AppendASCII(
125            chrome::kInitialProfile);
126  }
127  return ShellIntegration::GetAppListAppModelIdForProfile(initial_profile_path);
128}
129
130#if defined(GOOGLE_CHROME_BUILD)
131void SetDidRunForNDayActiveStats() {
132  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
133  base::FilePath exe_path;
134  if (!PathService::Get(base::DIR_EXE, &exe_path)) {
135    NOTREACHED();
136    return;
137  }
138  bool system_install =
139      !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
140  // Using Chrome Binary dist: Chrome dist may not exist for the legacy
141  // App Launcher, and App Launcher dist may be "shadow", which does not
142  // contain the information needed to determine multi-install.
143  // Edge case involving Canary: crbug/239163.
144  BrowserDistribution* chrome_binaries_dist =
145      BrowserDistribution::GetSpecificDistribution(
146          BrowserDistribution::CHROME_BINARIES);
147  if (chrome_binaries_dist &&
148      InstallUtil::IsMultiInstall(chrome_binaries_dist, system_install)) {
149    UpdatingAppRegistrationData app_launcher_reg_data(
150        installer::kAppLauncherGuid);
151    GoogleUpdateSettings::UpdateDidRunStateForApp(
152        app_launcher_reg_data, true /* did_run */);
153  }
154}
155#endif  // GOOGLE_CHROME_BUILD
156
157// The start menu shortcut is created on first run by users that are
158// upgrading. The desktop and taskbar shortcuts are created the first time the
159// user enables the app list. The taskbar shortcut is created in
160// |user_data_dir| and will use a Windows Application Model Id of
161// |app_model_id|. This runs on the FILE thread and not in the blocking IO
162// thread pool as there are other tasks running (also on the FILE thread)
163// which fiddle with shortcut icons
164// (ShellIntegration::MigrateWin7ShortcutsOnPath). Having different threads
165// fiddle with the same shortcuts could cause race issues.
166void CreateAppListShortcuts(
167    const base::FilePath& user_data_dir,
168    const base::string16& app_model_id,
169    const web_app::ShortcutLocations& creation_locations) {
170  DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
171
172  // Shortcut paths under which to create shortcuts.
173  std::vector<base::FilePath> shortcut_paths =
174      web_app::internals::GetShortcutPaths(creation_locations);
175
176  bool pin_to_taskbar = creation_locations.in_quick_launch_bar &&
177                        (base::win::GetVersion() >= base::win::VERSION_WIN7);
178
179  // Create a shortcut in the |user_data_dir| for taskbar pinning.
180  if (pin_to_taskbar)
181    shortcut_paths.push_back(user_data_dir);
182  bool success = true;
183
184  base::FilePath chrome_exe;
185  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
186    NOTREACHED();
187    return;
188  }
189
190  base::string16 app_list_shortcut_name = GetAppListShortcutName();
191
192  base::string16 wide_switches(GetAppListCommandLine().GetArgumentsString());
193
194  base::win::ShortcutProperties shortcut_properties;
195  shortcut_properties.set_target(chrome_exe);
196  shortcut_properties.set_working_dir(chrome_exe.DirName());
197  shortcut_properties.set_arguments(wide_switches);
198  shortcut_properties.set_description(app_list_shortcut_name);
199  shortcut_properties.set_icon(chrome_exe, GetAppListIconIndex());
200  shortcut_properties.set_app_id(app_model_id);
201
202  for (size_t i = 0; i < shortcut_paths.size(); ++i) {
203    base::FilePath shortcut_file =
204        shortcut_paths[i].Append(app_list_shortcut_name).
205            AddExtension(installer::kLnkExt);
206    if (!base::PathExists(shortcut_file.DirName()) &&
207        !base::CreateDirectory(shortcut_file.DirName())) {
208      NOTREACHED();
209      return;
210    }
211    success = success && base::win::CreateOrUpdateShortcutLink(
212        shortcut_file, shortcut_properties,
213        base::win::SHORTCUT_CREATE_ALWAYS);
214  }
215
216  if (success && pin_to_taskbar) {
217    base::FilePath shortcut_to_pin =
218        user_data_dir.Append(app_list_shortcut_name).
219            AddExtension(installer::kLnkExt);
220    success = base::win::TaskbarPinShortcutLink(
221        shortcut_to_pin.value().c_str()) && success;
222  }
223}
224
225// Customizes the app list |hwnd| for Windows (eg: disable aero peek, set up
226// restart params).
227void SetWindowAttributes(HWND hwnd) {
228  if (base::win::GetVersion() > base::win::VERSION_VISTA) {
229    // Disable aero peek. Without this, hovering over the taskbar popup puts
230    // Windows into a mode for switching between windows in the same
231    // application. The app list has just one window, so it is just distracting.
232    BOOL disable_value = TRUE;
233    ::DwmSetWindowAttribute(hwnd,
234                            DWMWA_DISALLOW_PEEK,
235                            &disable_value,
236                            sizeof(disable_value));
237  }
238
239  ui::win::SetAppIdForWindow(GetAppModelId(), hwnd);
240  CommandLine relaunch = GetAppListCommandLine();
241  base::string16 app_name(GetAppListShortcutName());
242  ui::win::SetRelaunchDetailsForWindow(
243      relaunch.GetCommandLineString(), app_name, hwnd);
244  ::SetWindowText(hwnd, app_name.c_str());
245  base::string16 icon_path = GetAppListIconPath();
246  ui::win::SetAppIconForWindow(icon_path, hwnd);
247}
248
249}  // namespace
250
251// static
252AppListServiceWin* AppListServiceWin::GetInstance() {
253  return Singleton<AppListServiceWin,
254                   LeakySingletonTraits<AppListServiceWin> >::get();
255}
256
257AppListServiceWin::AppListServiceWin()
258    : AppListServiceViews(scoped_ptr<AppListControllerDelegate>(
259          new AppListControllerDelegateWin(this))),
260      enable_app_list_on_next_init_(false) {
261}
262
263AppListServiceWin::~AppListServiceWin() {
264}
265
266void AppListServiceWin::ShowForProfile(Profile* requested_profile) {
267  AppListServiceViews::ShowForProfile(requested_profile);
268
269#if defined(GOOGLE_CHROME_BUILD)
270  content::BrowserThread::PostBlockingPoolTask(
271      FROM_HERE, base::Bind(SetDidRunForNDayActiveStats));
272#endif  // GOOGLE_CHROME_BUILD
273}
274
275void AppListServiceWin::OnLoadProfileForWarmup(Profile* initial_profile) {
276  if (!IsWarmupNeeded())
277    return;
278
279  base::Time before_warmup(base::Time::Now());
280  shower().WarmupForProfile(initial_profile);
281  UMA_HISTOGRAM_TIMES("Apps.AppListWarmupDuration",
282                      base::Time::Now() - before_warmup);
283}
284
285void AppListServiceWin::SetAppListNextPaintCallback(void (*callback)()) {
286  if (shower().app_list())
287    shower().app_list()->SetNextPaintCallback(base::Bind(callback));
288  else
289    next_paint_callback_ = base::Bind(callback);
290}
291
292void AppListServiceWin::HandleFirstRun() {
293  PrefService* local_state = g_browser_process->local_state();
294  // If the app list is already enabled during first run, then the user had
295  // opted in to the app launcher before uninstalling, so we re-enable to
296  // restore shortcuts to the app list.
297  // Note we can't directly create the shortcuts here because the IO thread
298  // hasn't been created yet.
299  enable_app_list_on_next_init_ = local_state->GetBoolean(
300      prefs::kAppLauncherHasBeenEnabled);
301}
302
303void AppListServiceWin::Init(Profile* initial_profile) {
304  if (enable_app_list_on_next_init_) {
305    enable_app_list_on_next_init_ = false;
306    EnableAppList(initial_profile, ENABLE_ON_REINSTALL);
307    CreateShortcut();
308  }
309
310  ScheduleWarmup();
311
312  MigrateAppLauncherEnabledPref();
313  AppListServiceViews::Init(initial_profile);
314}
315
316void AppListServiceWin::CreateShortcut() {
317  // Check if the app launcher shortcuts have ever been created before.
318  // Shortcuts should only be created once. If the user unpins the taskbar
319  // shortcut, they can restore it by pinning the start menu or desktop
320  // shortcut.
321  web_app::ShortcutLocations shortcut_locations;
322  shortcut_locations.on_desktop = true;
323  shortcut_locations.in_quick_launch_bar = true;
324  shortcut_locations.applications_menu_location =
325      web_app::APP_MENU_LOCATION_SUBDIR_CHROME;
326  base::FilePath user_data_dir(
327      g_browser_process->profile_manager()->user_data_dir());
328
329  content::BrowserThread::PostTask(
330      content::BrowserThread::FILE,
331      FROM_HERE,
332      base::Bind(&CreateAppListShortcuts,
333                 user_data_dir, GetAppModelId(), shortcut_locations));
334}
335
336void AppListServiceWin::ScheduleWarmup() {
337  // Post a task to create the app list. This is posted to not impact startup
338  // time.
339  const int kInitWindowDelay = 30;
340  base::MessageLoop::current()->PostDelayedTask(
341      FROM_HERE,
342      base::Bind(&AppListServiceWin::LoadProfileForWarmup,
343                 base::Unretained(this)),
344      base::TimeDelta::FromSeconds(kInitWindowDelay));
345}
346
347bool AppListServiceWin::IsWarmupNeeded() {
348  if (!g_browser_process || g_browser_process->IsShuttingDown() ||
349      browser_shutdown::IsTryingToQuit()) {
350    return false;
351  }
352
353  // We only need to initialize the view if there's no view already created and
354  // there's no profile loading to be shown.
355  return !shower().HasView() && !profile_loader().IsAnyProfileLoading();
356}
357
358void AppListServiceWin::LoadProfileForWarmup() {
359  if (!IsWarmupNeeded())
360    return;
361
362  ProfileManager* profile_manager = g_browser_process->profile_manager();
363  base::FilePath profile_path(GetProfilePath(profile_manager->user_data_dir()));
364
365  profile_loader().LoadProfileInvalidatingOtherLoads(
366      profile_path,
367      base::Bind(&AppListServiceWin::OnLoadProfileForWarmup,
368                 base::Unretained(this)));
369}
370
371void AppListServiceWin::OnViewBeingDestroyed() {
372  activation_tracker_.reset();
373  AppListServiceViews::OnViewBeingDestroyed();
374}
375
376void AppListServiceWin::OnViewCreated() {
377  if (!next_paint_callback_.is_null()) {
378    shower().app_list()->SetNextPaintCallback(next_paint_callback_);
379    next_paint_callback_.Reset();
380  }
381  SetWindowAttributes(shower().app_list()->GetHWND());
382  activation_tracker_.reset(new ActivationTrackerWin(this));
383}
384
385void AppListServiceWin::OnViewDismissed() {
386  activation_tracker_->OnViewHidden();
387}
388
389void AppListServiceWin::MoveNearCursor(app_list::AppListView* view) {
390  AppListWin::MoveNearCursor(view);
391}
392