app_list_service_mac.mm revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch// Copyright 2013 The Chromium Authors. All rights reserved.
28bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
38bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// found in the LICENSE file.
48bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
58bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include <ApplicationServices/ApplicationServices.h>
68bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import <Cocoa/Cocoa.h>
78bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
88bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "apps/app_shim/app_shim_handler_mac.h"
98bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/bind.h"
108bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/command_line.h"
118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/file_util.h"
121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/lazy_instance.h"
138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "base/mac/scoped_nsobject.h"
14c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/memory/singleton.h"
15c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/message_loop.h"
16c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "base/observer_list.h"
17c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include "chrome/browser/extensions/extension_service.h"
188bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/extensions/extension_system.h"
198bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service.h"
218bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service_impl.h"
228bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_view_delegate.h"
23f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "chrome/browser/ui/extensions/application_launch.h"
248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/ui/web_applications/web_app_ui.h"
25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "chrome/browser/web_applications/web_app.h"
268bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/web_applications/web_app_mac.h"
278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/chrome_switches.h"
288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/chrome_version_info.h"
298bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/common/mac/app_mode_common.h"
308bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "content/public/browser/browser_thread.h"
318bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "grit/chrome_unscaled_resources.h"
328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "grit/google_chrome_strings.h"
338bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_view_controller.h"
348bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_window_controller.h"
358bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
368bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
378bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/gfx/display.h"
388bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "ui/gfx/screen.h"
398bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)namespace gfx {
418bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)class ImageSkia;
428bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}
438bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
448bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)namespace {
458bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
468bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// AppListServiceMac manages global resources needed for the app list to
478bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// operate, and controls when the app list is opened and closed.
488bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)class AppListServiceMac : public AppListServiceImpl,
498bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                          public apps::AppShimHandler {
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public:
518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual ~AppListServiceMac() {}
528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  static AppListServiceMac* GetInstance() {
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return Singleton<AppListServiceMac,
558bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                     LeakySingletonTraits<AppListServiceMac> >::get();
568bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  }
578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
588bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  void CreateAppList(Profile* profile);
598bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  void ShowWindowNearDock();
608bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
618bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // AppListService overrides:
628bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void Init(Profile* initial_profile) OVERRIDE;
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  virtual void ShowAppList(Profile* requested_profile) OVERRIDE;
648bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void DismissAppList() OVERRIDE;
658bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual bool IsAppListVisible() const OVERRIDE;
668bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void EnableAppList(Profile* initial_profile) OVERRIDE;
678bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE;
688bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
69a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  // AppListServiceImpl override:
708bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnSigninStatusChanged() OVERRIDE;
718bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
728bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // AppShimHandler overrides:
738bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnShimLaunch(apps::AppShimHandler::Host* host,
748bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                            apps::AppShimLaunchType launch_type) OVERRIDE;
758bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnShimClose(apps::AppShimHandler::Host* host) OVERRIDE;
768bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnShimFocus(apps::AppShimHandler::Host* host,
778bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                           apps::AppShimFocusType focus_type) OVERRIDE;
788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnShimSetHidden(apps::AppShimHandler::Host* host,
798bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                               bool hidden) OVERRIDE;
808bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void OnShimQuit(apps::AppShimHandler::Host* host) OVERRIDE;
818bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
828bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) private:
838bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  friend struct DefaultSingletonTraits<AppListServiceMac>;
848bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
85f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  AppListServiceMac() {}
865d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
875d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  base::scoped_nsobject<AppListWindowController> window_controller_;
888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // App shim hosts observing when the app list is dismissed. In normal user
908bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // usage there should only be one. However, it can't be guaranteed, so use
918bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // an ObserverList rather than handling corner cases.
928bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  ObserverList<apps::AppShimHandler::Host> observers_;
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(AppListServiceMac);
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)};
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class AppListControllerDelegateCocoa : public AppListControllerDelegate {
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) public:
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  AppListControllerDelegateCocoa();
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  virtual ~AppListControllerDelegateCocoa();
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) private:
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  // AppListControllerDelegate overrides:
1048bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void DismissView() OVERRIDE;
1058bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual gfx::NativeWindow GetAppListWindow() OVERRIDE;
1068bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual bool CanPin() OVERRIDE;
1078bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual bool CanDoCreateShortcutsFlow(bool is_platform_app) OVERRIDE;
1088bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void DoCreateShortcutsFlow(Profile* profile,
1098bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                                     const std::string& extension_id) OVERRIDE;
1108bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void ActivateApp(Profile* profile,
1118bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                           const extensions::Extension* extension,
1128bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                           int event_flags) OVERRIDE;
1138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  virtual void LaunchApp(Profile* profile,
1148bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                         const extensions::Extension* extension,
1158bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)                         int event_flags) OVERRIDE;
1168bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
1178bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  DISALLOW_COPY_AND_ASSIGN(AppListControllerDelegateCocoa);
1188bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)};
1198bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
1208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)ShellIntegration::ShortcutInfo GetAppListShortcutInfo(
1218bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    const base::FilePath& profile_path) {
1228bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  ShellIntegration::ShortcutInfo shortcut_info;
1238bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
124effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
125effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    shortcut_info.title =
126effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY);
127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  } else {
1288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME);
1298bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  }
1308bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
1318bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  shortcut_info.extension_id = app_mode::kAppListModeId;
1328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  shortcut_info.description = shortcut_info.title;
1330f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)  shortcut_info.profile_path = profile_path;
1340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
1351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return shortcut_info;
1360f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)}
1370f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
1380f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)void CreateAppListShim(const base::FilePath& profile_path) {
1391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
1408bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  WebApplicationInfo web_app_info;
141  ShellIntegration::ShortcutInfo shortcut_info =
142      GetAppListShortcutInfo(profile_path);
143
144  ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
145  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
146  if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
147#if defined(GOOGLE_CHROME_BUILD)
148    shortcut_info.favicon.Add(
149        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16));
150    shortcut_info.favicon.Add(
151        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32));
152    shortcut_info.favicon.Add(
153        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128));
154    shortcut_info.favicon.Add(
155        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256));
156#else
157    NOTREACHED();
158#endif
159  } else {
160    shortcut_info.favicon.Add(
161        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
162    shortcut_info.favicon.Add(
163        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
164    shortcut_info.favicon.Add(
165        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128));
166    shortcut_info.favicon.Add(
167        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
168  }
169
170  // TODO(tapted): Create a dock icon using chrome/browser/mac/dock.h .
171  web_app::CreateShortcuts(shortcut_info,
172                           ShellIntegration::ShortcutLocations(),
173                           web_app::ALLOW_DUPLICATE_SHORTCUTS);
174}
175
176// Check that there is an app list shim. If enabling and there is not, make one.
177// If disabling with --enable-app-list-shim=0, and there is one, delete it.
178void CheckAppListShimOnFileThread(const base::FilePath& profile_path) {
179  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
180  const bool enable =
181      CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppListShim);
182  base::FilePath install_path = web_app::GetAppInstallPath(
183      GetAppListShortcutInfo(profile_path));
184  if (enable == base::PathExists(install_path))
185    return;
186
187  if (enable) {
188    content::BrowserThread::PostTask(
189        content::BrowserThread::UI, FROM_HERE,
190        base::Bind(&CreateAppListShim, profile_path));
191    return;
192  }
193
194  // Sanity check because deleting things recursively is scary.
195  CHECK(install_path.MatchesExtension(".app"));
196  base::DeleteFile(install_path, true /* recursive */);
197}
198
199void CreateShortcutsInDefaultLocation(
200    const ShellIntegration::ShortcutInfo& shortcut_info) {
201  web_app::CreateShortcuts(shortcut_info,
202                           ShellIntegration::ShortcutLocations(),
203                           web_app::ALLOW_DUPLICATE_SHORTCUTS);
204}
205
206AppListControllerDelegateCocoa::AppListControllerDelegateCocoa() {}
207
208AppListControllerDelegateCocoa::~AppListControllerDelegateCocoa() {}
209
210void AppListControllerDelegateCocoa::DismissView() {
211  AppListServiceMac::GetInstance()->DismissAppList();
212}
213
214gfx::NativeWindow AppListControllerDelegateCocoa::GetAppListWindow() {
215  return AppListServiceMac::GetInstance()->GetAppListWindow();
216}
217
218bool AppListControllerDelegateCocoa::CanPin() {
219  return false;
220}
221
222bool AppListControllerDelegateCocoa::CanDoCreateShortcutsFlow(
223    bool is_platform_app) {
224  return is_platform_app &&
225      CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableAppShims);
226}
227
228void AppListControllerDelegateCocoa::DoCreateShortcutsFlow(
229    Profile* profile, const std::string& extension_id) {
230  ExtensionService* service =
231      extensions::ExtensionSystem::Get(profile)->extension_service();
232  DCHECK(service);
233  const extensions::Extension* extension =
234      service->GetInstalledExtension(extension_id);
235  DCHECK(extension);
236
237  web_app::UpdateShortcutInfoAndIconForApp(
238      *extension, profile, base::Bind(&CreateShortcutsInDefaultLocation));
239}
240
241void AppListControllerDelegateCocoa::ActivateApp(
242    Profile* profile, const extensions::Extension* extension, int event_flags) {
243  LaunchApp(profile, extension, event_flags);
244}
245
246void AppListControllerDelegateCocoa::LaunchApp(
247    Profile* profile, const extensions::Extension* extension, int event_flags) {
248  chrome::OpenApplication(chrome::AppLaunchParams(
249      profile, extension, NEW_FOREGROUND_TAB));
250}
251
252void AppListServiceMac::CreateAppList(Profile* requested_profile) {
253  if (profile() == requested_profile)
254    return;
255
256  // The Objective C objects might be released at some unknown point in the
257  // future, so explicitly clear references to C++ objects.
258  [[window_controller_ appListViewController]
259      setDelegate:scoped_ptr<app_list::AppListViewDelegate>()];
260
261  SetProfile(requested_profile);
262  scoped_ptr<app_list::AppListViewDelegate> delegate(
263      new AppListViewDelegate(new AppListControllerDelegateCocoa(), profile()));
264  window_controller_.reset([[AppListWindowController alloc] init]);
265  [[window_controller_ appListViewController] setDelegate:delegate.Pass()];
266}
267
268void AppListServiceMac::Init(Profile* initial_profile) {
269  // On Mac, Init() is called multiple times for a process: any time there is no
270  // browser window open and a new window is opened, and during process startup
271  // to handle the silent launch case (e.g. for app shims). In the startup case,
272  // a profile has not yet been determined so |initial_profile| will be NULL.
273  if (initial_profile) {
274    static bool checked_shim = false;
275    if (!checked_shim) {
276      checked_shim = true;
277      content::BrowserThread::PostTask(
278          content::BrowserThread::FILE, FROM_HERE,
279          base::Bind(&CheckAppListShimOnFileThread,
280                     initial_profile->GetPath()));
281    }
282  }
283
284  static bool init_called = false;
285  if (init_called)
286    return;
287
288  init_called = true;
289  apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId,
290                                        AppListServiceMac::GetInstance());
291}
292
293void AppListServiceMac::ShowAppList(Profile* requested_profile) {
294  InvalidatePendingProfileLoads();
295
296  if (IsAppListVisible() && (requested_profile == profile())) {
297    ShowWindowNearDock();
298    return;
299  }
300
301  SaveProfilePathToLocalState(requested_profile->GetPath());
302
303  DismissAppList();
304  CreateAppList(requested_profile);
305  ShowWindowNearDock();
306}
307
308void AppListServiceMac::DismissAppList() {
309  if (!IsAppListVisible())
310    return;
311
312  [[window_controller_ window] close];
313
314  FOR_EACH_OBSERVER(apps::AppShimHandler::Host,
315                    observers_,
316                    OnAppClosed());
317}
318
319bool AppListServiceMac::IsAppListVisible() const {
320  return [[window_controller_ window] isVisible];
321}
322
323void AppListServiceMac::EnableAppList(Profile* initial_profile) {
324  // TODO(tapted): Implement enable logic here for OSX.
325}
326
327NSWindow* AppListServiceMac::GetAppListWindow() {
328  return [window_controller_ window];
329}
330
331void AppListServiceMac::OnSigninStatusChanged() {
332  [[window_controller_ appListViewController] onSigninStatusChanged];
333}
334
335void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host,
336                                     apps::AppShimLaunchType launch_type) {
337  ShowForSavedProfile();
338  observers_.AddObserver(host);
339  host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_SUCCESS);
340}
341
342void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {
343  observers_.RemoveObserver(host);
344  DismissAppList();
345}
346
347void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host,
348                                    apps::AppShimFocusType focus_type) {
349  DismissAppList();
350}
351
352void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host,
353                                        bool hidden) {}
354
355void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {
356  DismissAppList();
357}
358
359enum DockLocation {
360  DockLocationOtherDisplay,
361  DockLocationBottom,
362  DockLocationLeft,
363  DockLocationRight,
364};
365
366DockLocation DockLocationInDisplay(const gfx::Display& display) {
367  // Assume the dock occupies part of the work area either on the left, right or
368  // bottom of the display. Note in the autohide case, it is always 4 pixels.
369  const gfx::Rect work_area = display.work_area();
370  const gfx::Rect display_bounds = display.bounds();
371  if (work_area.bottom() != display_bounds.bottom())
372    return DockLocationBottom;
373
374  if (work_area.x() != display_bounds.x())
375    return DockLocationLeft;
376
377  if (work_area.right() != display_bounds.right())
378    return DockLocationRight;
379
380  return DockLocationOtherDisplay;
381}
382
383// If |work_area_edge| is too close to the |screen_edge| (e.g. autohide dock),
384// adjust |anchor| away from the edge by a constant amount to reduce overlap and
385// ensure the dock icon can still be clicked to dismiss the app list.
386int AdjustPointForDynamicDock(int anchor, int screen_edge, int work_area_edge) {
387  const int kAutohideDockThreshold = 10;
388  const int kExtraDistance = 50;  // A dock with 40 items is about this size.
389  if (abs(work_area_edge - screen_edge) > kAutohideDockThreshold)
390    return anchor;
391
392  return anchor +
393      (screen_edge < work_area_edge ? kExtraDistance : -kExtraDistance);
394}
395
396NSPoint GetAppListWindowOrigin(NSWindow* window) {
397  gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]);
398  // Ensure y coordinates are flipped back into AppKit's coordinate system.
399  const CGFloat max_y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]);
400  if (!CGCursorIsVisible()) {
401    // If Chrome is the active application, display on the same display as
402    // Chrome's keyWindow since this will catch activations triggered, e.g, via
403    // WebStore install. If another application is active, OSX doesn't provide a
404    // reliable way to get the display in use. Fall back to the primary display
405    // since it has the menu bar and is likely to be correct, e.g., for
406    // activations from Spotlight.
407    const gfx::NativeView key_view = [[NSApp keyWindow] contentView];
408    const gfx::Rect work_area = key_view && [NSApp isActive] ?
409        screen->GetDisplayNearestWindow(key_view).work_area() :
410        screen->GetPrimaryDisplay().work_area();
411    return NSMakePoint(work_area.x(), max_y - work_area.bottom());
412  }
413
414  gfx::Point anchor = screen->GetCursorScreenPoint();
415  const gfx::Display display = screen->GetDisplayNearestPoint(anchor);
416  const DockLocation dock_location = DockLocationInDisplay(display);
417  const gfx::Rect display_bounds = display.bounds();
418
419  if (dock_location == DockLocationOtherDisplay) {
420    // Just display at the bottom-left of the display the cursor is on.
421    return NSMakePoint(display_bounds.x(), max_y - display_bounds.bottom());
422  }
423
424  // Anchor the center of the window in a region that prevents the window
425  // showing outside of the work area.
426  const NSSize window_size = [window frame].size;
427  const gfx::Rect work_area = display.work_area();
428  gfx::Rect anchor_area = work_area;
429  anchor_area.Inset(window_size.width / 2, window_size.height / 2);
430  anchor.SetToMax(anchor_area.origin());
431  anchor.SetToMin(anchor_area.bottom_right());
432
433  // Move anchor to the dock, keeping the other axis aligned with the cursor.
434  switch (dock_location) {
435    case DockLocationBottom:
436      anchor.set_y(AdjustPointForDynamicDock(
437          anchor_area.bottom(), display_bounds.bottom(), work_area.bottom()));
438      break;
439    case DockLocationLeft:
440      anchor.set_x(AdjustPointForDynamicDock(
441          anchor_area.x(), display_bounds.x(), work_area.x()));
442      break;
443    case DockLocationRight:
444      anchor.set_x(AdjustPointForDynamicDock(
445          anchor_area.right(), display_bounds.right(), work_area.right()));
446      break;
447    default:
448      NOTREACHED();
449  }
450
451  return NSMakePoint(
452      anchor.x() - window_size.width / 2,
453      max_y - anchor.y() - window_size.height / 2);
454}
455
456void AppListServiceMac::ShowWindowNearDock() {
457  NSWindow* window = GetAppListWindow();
458  DCHECK(window);
459  [window setFrameOrigin:GetAppListWindowOrigin(window)];
460  [window makeKeyAndOrderFront:nil];
461  [NSApp activateIgnoringOtherApps:YES];
462}
463
464}  // namespace
465
466// static
467AppListService* AppListService::Get() {
468  return AppListServiceMac::GetInstance();
469}
470
471// static
472void AppListService::InitAll(Profile* initial_profile) {
473  Get()->Init(initial_profile);
474}
475