app_list_service_mac.mm revision 116680a4aac90f2aa7413d9095a592090648e557
12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
32a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// found in the LICENSE file.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#import "chrome/browser/ui/app_list/app_list_service_mac.h"
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <ApplicationServices/ApplicationServices.h>
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import <Cocoa/Cocoa.h>
9f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "base/bind.h"
1146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "base/command_line.h"
124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/file_util.h"
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/lazy_instance.h"
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/mac/mac_util.h"
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/memory/singleton.h"
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/message_loop/message_loop.h"
1703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "base/prefs/pref_service.h"
1803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#import "chrome/browser/app_controller_mac.h"
1903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/browser/browser_process.h"
2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "chrome/browser/extensions/extension_service.h"
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/profiles/profile_manager.h"
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h"
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_positioner.h"
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service.h"
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_service_impl.h"
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_util.h"
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_view_delegate.h"
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/browser_commands.h"
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/ui/extensions/application_launch.h"
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/web_applications/web_app.h"
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/browser/web_applications/web_app_mac.h"
322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/chrome_switches.h"
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/chrome_version_info.h"
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/mac/app_mode_common.h"
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "chrome/common/pref_names.h"
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "content/public/browser/browser_thread.h"
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "extensions/browser/extension_system.h"
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "extensions/common/manifest_handlers/file_handler_info.h"
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/chrome_unscaled_resources.h"
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "grit/google_chrome_strings.h"
422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "net/base/url_util.h"
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_view_controller.h"
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#import "ui/app_list/cocoa/app_list_window_controller.h"
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/app_list/search_box_model.h"
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "ui/gfx/display.h"
4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)#include "ui/gfx/screen.h"
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace gfx {
522a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)class ImageSkia;
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
5446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)// Controller for animations that show or hide the app list.
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)@interface AppListAnimationController : NSObject<NSAnimationDelegate> {
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) @private
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // When closing, the window to close. Retained until the animation ends.
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::scoped_nsobject<NSWindow> window_;
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // The animation started and owned by |self|. Reset when the animation ends.
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  base::scoped_nsobject<NSViewAnimation> animation_;
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
64// Returns whether |window_| is scheduled to be closed when the animation ends.
65- (BOOL)isClosing;
66
67// Animate |window| to show or close it, after cancelling any current animation.
68// Translates from the current location to |targetOrigin| and fades in or out.
69- (void)animateWindow:(NSWindow*)window
70         targetOrigin:(NSPoint)targetOrigin
71              closing:(BOOL)closing;
72
73// Called on the UI thread once the animation has completed to reset the
74// animation state, close the window (if it is a close animation), and possibly
75// terminate Chrome.
76- (void)cleanupOnUIThread;
77
78@end
79
80namespace {
81
82// Version of the app list shortcut version installed.
83const int kShortcutVersion = 2;
84
85// Duration of show and hide animations.
86const NSTimeInterval kAnimationDuration = 0.2;
87
88// Distance towards the screen edge that the app list moves from when showing.
89const CGFloat kDistanceMovedOnShow = 20;
90
91web_app::ShortcutInfo GetAppListShortcutInfo(
92    const base::FilePath& profile_path) {
93  web_app::ShortcutInfo shortcut_info;
94  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
95  if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
96    shortcut_info.title =
97        l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY);
98  } else {
99    shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME);
100  }
101
102  shortcut_info.extension_id = app_mode::kAppListModeId;
103  shortcut_info.description = shortcut_info.title;
104  shortcut_info.profile_path = profile_path;
105
106  return shortcut_info;
107}
108
109void CreateAppListShim(const base::FilePath& profile_path) {
110  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
111  WebApplicationInfo web_app_info;
112  web_app::ShortcutInfo shortcut_info =
113      GetAppListShortcutInfo(profile_path);
114
115  ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
116  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
117  if (channel == chrome::VersionInfo::CHANNEL_CANARY) {
118#if defined(GOOGLE_CHROME_BUILD)
119    shortcut_info.favicon.Add(
120        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16));
121    shortcut_info.favicon.Add(
122        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32));
123    shortcut_info.favicon.Add(
124        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128));
125    shortcut_info.favicon.Add(
126        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256));
127#else
128    NOTREACHED();
129#endif
130  } else {
131    shortcut_info.favicon.Add(
132        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16));
133    shortcut_info.favicon.Add(
134        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32));
135    shortcut_info.favicon.Add(
136        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128));
137    shortcut_info.favicon.Add(
138        *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256));
139  }
140
141  web_app::ShortcutLocations shortcut_locations;
142  PrefService* local_state = g_browser_process->local_state();
143  int installed_version =
144      local_state->GetInteger(prefs::kAppLauncherShortcutVersion);
145
146  // If this is a first-time install, add a dock icon. Otherwise just update
147  // the target, and wait for OSX to refresh its icon caches. This might not
148  // occur until a reboot, but OSX does not offer a nicer way. Deleting cache
149  // files on disk and killing processes can easily result in icon corruption.
150  if (installed_version == 0)
151    shortcut_locations.in_quick_launch_bar = true;
152
153  web_app::CreateShortcutsWithInfo(web_app::SHORTCUT_CREATION_AUTOMATED,
154                                   shortcut_locations,
155                                   shortcut_info,
156                                   extensions::FileHandlersInfo());
157
158  local_state->SetInteger(prefs::kAppLauncherShortcutVersion,
159                          kShortcutVersion);
160}
161
162NSRunningApplication* ActiveApplicationNotChrome() {
163  NSArray* applications = [[NSWorkspace sharedWorkspace] runningApplications];
164  for (NSRunningApplication* application in applications) {
165    if (![application isActive])
166      continue;
167
168    if ([application isEqual:[NSRunningApplication currentApplication]])
169      return nil;  // Chrome is active.
170
171    return application;
172  }
173
174  return nil;
175}
176
177// Determines which screen edge the dock is aligned to.
178AppListPositioner::ScreenEdge DockLocationInDisplay(
179    const gfx::Display& display) {
180  // Assume the dock occupies part of the work area either on the left, right or
181  // bottom of the display. Note in the autohide case, it is always 4 pixels.
182  const gfx::Rect work_area = display.work_area();
183  const gfx::Rect display_bounds = display.bounds();
184  if (work_area.bottom() != display_bounds.bottom())
185    return AppListPositioner::SCREEN_EDGE_BOTTOM;
186
187  if (work_area.x() != display_bounds.x())
188    return AppListPositioner::SCREEN_EDGE_LEFT;
189
190  if (work_area.right() != display_bounds.right())
191    return AppListPositioner::SCREEN_EDGE_RIGHT;
192
193  return AppListPositioner::SCREEN_EDGE_UNKNOWN;
194}
195
196// If |display|'s work area is too close to its boundary on |dock_edge|, adjust
197// the work area away from the edge by a constant amount to reduce overlap and
198// ensure the dock icon can still be clicked to dismiss the app list.
199void AdjustWorkAreaForDock(const gfx::Display& display,
200                           AppListPositioner* positioner,
201                           AppListPositioner::ScreenEdge dock_edge) {
202  const int kAutohideDockThreshold = 10;
203  const int kExtraDistance = 50;  // A dock with 40 items is about this size.
204
205  const gfx::Rect work_area = display.work_area();
206  const gfx::Rect display_bounds = display.bounds();
207
208  switch (dock_edge) {
209    case AppListPositioner::SCREEN_EDGE_LEFT:
210      if (work_area.x() - display_bounds.x() <= kAutohideDockThreshold)
211        positioner->WorkAreaInset(kExtraDistance, 0, 0, 0);
212      break;
213    case AppListPositioner::SCREEN_EDGE_RIGHT:
214      if (display_bounds.right() - work_area.right() <= kAutohideDockThreshold)
215        positioner->WorkAreaInset(0, 0, kExtraDistance, 0);
216      break;
217    case AppListPositioner::SCREEN_EDGE_BOTTOM:
218      if (display_bounds.bottom() - work_area.bottom() <=
219          kAutohideDockThreshold) {
220        positioner->WorkAreaInset(0, 0, 0, kExtraDistance);
221      }
222      break;
223    case AppListPositioner::SCREEN_EDGE_UNKNOWN:
224    case AppListPositioner::SCREEN_EDGE_TOP:
225      NOTREACHED();
226      break;
227  }
228}
229
230void GetAppListWindowOrigins(
231    NSWindow* window, NSPoint* target_origin, NSPoint* start_origin) {
232  gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]);
233  // Ensure y coordinates are flipped back into AppKit's coordinate system.
234  bool cursor_is_visible = CGCursorIsVisible();
235  gfx::Display display;
236  gfx::Point cursor;
237  if (!cursor_is_visible) {
238    // If Chrome is the active application, display on the same display as
239    // Chrome's keyWindow since this will catch activations triggered, e.g, via
240    // WebStore install. If another application is active, OSX doesn't provide a
241    // reliable way to get the display in use. Fall back to the primary display
242    // since it has the menu bar and is likely to be correct, e.g., for
243    // activations from Spotlight.
244    const gfx::NativeView key_view = [[NSApp keyWindow] contentView];
245    display = key_view && [NSApp isActive] ?
246        screen->GetDisplayNearestWindow(key_view) :
247        screen->GetPrimaryDisplay();
248  } else {
249    cursor = screen->GetCursorScreenPoint();
250    display = screen->GetDisplayNearestPoint(cursor);
251  }
252
253  const NSSize ns_window_size = [window frame].size;
254  gfx::Size window_size(ns_window_size.width, ns_window_size.height);
255  int primary_display_height =
256      NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]);
257  AppListServiceMac::FindAnchorPoint(window_size,
258                                     display,
259                                     primary_display_height,
260                                     cursor_is_visible,
261                                     cursor,
262                                     target_origin,
263                                     start_origin);
264}
265
266}  // namespace
267
268AppListServiceMac::AppListServiceMac()
269    : profile_(NULL),
270      controller_delegate_(new AppListControllerDelegateImpl(this)) {
271  animation_controller_.reset([[AppListAnimationController alloc] init]);
272}
273
274AppListServiceMac::~AppListServiceMac() {}
275
276// static
277AppListServiceMac* AppListServiceMac::GetInstance() {
278  return Singleton<AppListServiceMac,
279                   LeakySingletonTraits<AppListServiceMac> >::get();
280}
281
282// static
283void AppListServiceMac::FindAnchorPoint(const gfx::Size& window_size,
284                                        const gfx::Display& display,
285                                        int primary_display_height,
286                                        bool cursor_is_visible,
287                                        const gfx::Point& cursor,
288                                        NSPoint* target_origin,
289                                        NSPoint* start_origin) {
290  AppListPositioner positioner(display, window_size, 0);
291  AppListPositioner::ScreenEdge dock_location = DockLocationInDisplay(display);
292
293  gfx::Point anchor;
294  // Snap to the dock edge. If the cursor is greater than the window
295  // width/height away or not visible, anchor to the center of the dock.
296  // Otherwise, anchor to the cursor position.
297  if (dock_location == AppListPositioner::SCREEN_EDGE_UNKNOWN) {
298    anchor = positioner.GetAnchorPointForScreenCorner(
299        AppListPositioner::SCREEN_CORNER_BOTTOM_LEFT);
300  } else {
301    int snap_distance =
302        dock_location == AppListPositioner::SCREEN_EDGE_BOTTOM ||
303                dock_location == AppListPositioner::SCREEN_EDGE_TOP ?
304            window_size.height() :
305            window_size.width();
306    // Subtract the dock area since the display's default work_area will not
307    // subtract it if the dock is set to auto-hide, and the app list should
308    // never overlap the dock.
309    AdjustWorkAreaForDock(display, &positioner, dock_location);
310    if (!cursor_is_visible || positioner.GetCursorDistanceFromShelf(
311                                  dock_location, cursor) > snap_distance) {
312      anchor = positioner.GetAnchorPointForShelfCenter(dock_location);
313    } else {
314      anchor = positioner.GetAnchorPointForShelfCursor(dock_location, cursor);
315    }
316  }
317
318  *target_origin = NSMakePoint(
319      anchor.x() - window_size.width() / 2,
320      primary_display_height - anchor.y() - window_size.height() / 2);
321  *start_origin = *target_origin;
322
323  // If the launcher is anchored to the dock (regardless of whether the cursor
324  // is visible), animate in inwards from the edge of screen
325  switch (dock_location) {
326    case AppListPositioner::SCREEN_EDGE_UNKNOWN:
327      break;
328    case AppListPositioner::SCREEN_EDGE_LEFT:
329      start_origin->x -= kDistanceMovedOnShow;
330      break;
331    case AppListPositioner::SCREEN_EDGE_RIGHT:
332      start_origin->x += kDistanceMovedOnShow;
333      break;
334    case AppListPositioner::SCREEN_EDGE_TOP:
335      NOTREACHED();
336      break;
337    case AppListPositioner::SCREEN_EDGE_BOTTOM:
338      start_origin->y -= kDistanceMovedOnShow;
339      break;
340  }
341}
342
343void AppListServiceMac::Init(Profile* initial_profile) {
344  // On Mac, Init() is called multiple times for a process: any time there is no
345  // browser window open and a new window is opened, and during process startup
346  // to handle the silent launch case (e.g. for app shims). In the startup case,
347  // a profile has not yet been determined so |initial_profile| will be NULL.
348  static bool init_called_with_profile = false;
349  if (initial_profile && !init_called_with_profile) {
350    init_called_with_profile = true;
351    PerformStartupChecks(initial_profile);
352    PrefService* local_state = g_browser_process->local_state();
353    if (!IsAppLauncherEnabled()) {
354      local_state->SetInteger(prefs::kAppLauncherShortcutVersion, 0);
355    } else {
356      int installed_shortcut_version =
357          local_state->GetInteger(prefs::kAppLauncherShortcutVersion);
358
359      if (kShortcutVersion > installed_shortcut_version)
360        CreateShortcut();
361    }
362  }
363
364  static bool init_called = false;
365  if (init_called)
366    return;
367
368  init_called = true;
369  apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId,
370                                        AppListServiceMac::GetInstance());
371
372  // Handle the case where Chrome was not running and was started with the app
373  // launcher shim. The profile has not yet been loaded. To improve response
374  // times, start animating an empty window which will be populated via
375  // OnShimLaunch(). Note that if --silent-launch is not also passed, the window
376  // will instead populate via StartupBrowserCreator::Launch(). Shim-initiated
377  // launches will always have --silent-launch.
378  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList))
379    ShowWindowNearDock();
380}
381
382Profile* AppListServiceMac::GetCurrentAppListProfile() {
383  return profile_;
384}
385
386void AppListServiceMac::CreateForProfile(Profile* requested_profile) {
387  if (profile_ == requested_profile)
388    return;
389
390  profile_ = requested_profile;
391
392  if (!window_controller_)
393    window_controller_.reset([[AppListWindowController alloc] init]);
394
395  scoped_ptr<app_list::AppListViewDelegate> delegate(
396      new AppListViewDelegate(profile_, GetControllerDelegate()));
397  [[window_controller_ appListViewController] setDelegate:delegate.Pass()];
398}
399
400void AppListServiceMac::ShowForProfile(Profile* requested_profile) {
401  if (requested_profile->IsSupervised())
402    return;
403
404  InvalidatePendingProfileLoads();
405
406  if (requested_profile == profile_) {
407    ShowWindowNearDock();
408    return;
409  }
410
411  SetProfilePath(requested_profile->GetPath());
412  CreateForProfile(requested_profile);
413  ShowWindowNearDock();
414}
415
416void AppListServiceMac::DismissAppList() {
417  if (!IsAppListVisible())
418    return;
419
420  // If the app list is currently the main window, it will activate the next
421  // Chrome window when dismissed. But if a different application was active
422  // when the app list was shown, activate that instead.
423  base::scoped_nsobject<NSRunningApplication> prior_app;
424  if ([[window_controller_ window] isMainWindow])
425    prior_app.swap(previously_active_application_);
426  else
427    previously_active_application_.reset();
428
429  // If activation is successful, the app list will lose main status and try to
430  // close itself again. It can't be closed in this runloop iteration without
431  // OSX deciding to raise the next Chrome window, and _then_ activating the
432  // application on top. This also occurs if no activation option is given.
433  if ([prior_app activateWithOptions:NSApplicationActivateIgnoringOtherApps])
434    return;
435
436  [animation_controller_ animateWindow:[window_controller_ window]
437                          targetOrigin:last_start_origin_
438                               closing:YES];
439}
440
441bool AppListServiceMac::IsAppListVisible() const {
442  return [[window_controller_ window] isVisible] &&
443      ![animation_controller_ isClosing];
444}
445
446void AppListServiceMac::EnableAppList(Profile* initial_profile,
447                                      AppListEnableSource enable_source) {
448  AppListServiceImpl::EnableAppList(initial_profile, enable_source);
449  AppController* controller = [NSApp delegate];
450  [controller initAppShimMenuController];
451}
452
453void AppListServiceMac::CreateShortcut() {
454  CreateAppListShim(GetProfilePath(
455      g_browser_process->profile_manager()->user_data_dir()));
456}
457
458NSWindow* AppListServiceMac::GetAppListWindow() {
459  return [window_controller_ window];
460}
461
462AppListControllerDelegate* AppListServiceMac::GetControllerDelegate() {
463  return controller_delegate_.get();
464}
465
466void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host,
467                                     apps::AppShimLaunchType launch_type,
468                                     const std::vector<base::FilePath>& files) {
469  if (profile_ && IsAppListVisible()) {
470    DismissAppList();
471  } else {
472    // Start by showing a possibly empty window to handle the case where Chrome
473    // is running, but hasn't yet loaded the app launcher profile.
474    ShowWindowNearDock();
475    Show();
476  }
477
478  // Always close the shim process immediately.
479  host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_DUPLICATE_HOST);
480}
481
482void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {}
483
484void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host,
485                                    apps::AppShimFocusType focus_type,
486                                    const std::vector<base::FilePath>& files) {}
487
488void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host,
489                                        bool hidden) {}
490
491void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {}
492
493void AppListServiceMac::ShowWindowNearDock() {
494  if (IsAppListVisible())
495    return;
496
497  if (!window_controller_) {
498    // Note that this will start showing an unpopulated window, the caller needs
499    // to ensure it will be populated later.
500    window_controller_.reset([[AppListWindowController alloc] init]);
501  }
502
503  NSWindow* window = GetAppListWindow();
504  DCHECK(window);
505  NSPoint target_origin;
506  GetAppListWindowOrigins(window, &target_origin, &last_start_origin_);
507  [window setFrameOrigin:last_start_origin_];
508
509  // Before activating, see if an application other than Chrome is currently the
510  // active application, so that it can be reactivated when dismissing.
511  previously_active_application_.reset([ActiveApplicationNotChrome() retain]);
512
513  [animation_controller_ animateWindow:[window_controller_ window]
514                          targetOrigin:target_origin
515                               closing:NO];
516  [window makeKeyAndOrderFront:nil];
517  [NSApp activateIgnoringOtherApps:YES];
518  RecordAppListLaunch();
519}
520
521void AppListServiceMac::WindowAnimationDidEnd() {
522  [animation_controller_ cleanupOnUIThread];
523}
524
525// static
526AppListService* AppListService::Get(chrome::HostDesktopType desktop_type) {
527  return AppListServiceMac::GetInstance();
528}
529
530// static
531void AppListService::InitAll(Profile* initial_profile) {
532  AppListServiceMac::GetInstance()->Init(initial_profile);
533}
534
535@implementation AppListAnimationController
536
537- (BOOL)isClosing {
538  return !!window_;
539}
540
541- (void)animateWindow:(NSWindow*)window
542         targetOrigin:(NSPoint)targetOrigin
543              closing:(BOOL)closing {
544  // First, stop the existing animation, if there is one.
545  [animation_ stopAnimation];
546
547  NSRect targetFrame = [window frame];
548  targetFrame.origin = targetOrigin;
549
550  // NSViewAnimation has a quirk when setting the curve to NSAnimationEaseOut
551  // where it attempts to auto-reverse the animation. FadeOut becomes FadeIn
552  // (good), but FrameKey is also switched (bad). So |targetFrame| needs to be
553  // put on the StartFrameKey when using NSAnimationEaseOut for showing.
554  NSArray* animationArray = @[
555    @{
556      NSViewAnimationTargetKey : window,
557      NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect,
558      (closing ? NSViewAnimationEndFrameKey : NSViewAnimationStartFrameKey) :
559          [NSValue valueWithRect:targetFrame]
560    }
561  ];
562  animation_.reset(
563      [[NSViewAnimation alloc] initWithViewAnimations:animationArray]);
564  [animation_ setDuration:kAnimationDuration];
565  [animation_ setDelegate:self];
566
567  if (closing) {
568    [animation_ setAnimationCurve:NSAnimationEaseIn];
569    window_.reset([window retain]);
570  } else {
571    [window setAlphaValue:0.0f];
572    [animation_ setAnimationCurve:NSAnimationEaseOut];
573    window_.reset();
574  }
575  // Threaded animations are buggy on Snow Leopard. See http://crbug.com/335550.
576  // Note that in the non-threaded case, the animation won't start unless the
577  // UI runloop has spun up, so on <= Lion the animation will only animate if
578  // Chrome is already running.
579  if (base::mac::IsOSMountainLionOrLater())
580    [animation_ setAnimationBlockingMode:NSAnimationNonblockingThreaded];
581  else
582    [animation_ setAnimationBlockingMode:NSAnimationNonblocking];
583
584  [animation_ startAnimation];
585}
586
587- (void)cleanupOnUIThread {
588  bool closing = [self isClosing];
589  [window_ close];
590  window_.reset();
591  animation_.reset();
592
593  if (closing)
594    apps::AppShimHandler::MaybeTerminate();
595}
596
597- (void)animationDidEnd:(NSAnimation*)animation {
598  content::BrowserThread::PostTask(
599      content::BrowserThread::UI,
600      FROM_HERE,
601      base::Bind(&AppListServiceMac::WindowAnimationDidEnd,
602                 base::Unretained(AppListServiceMac::GetInstance())));
603}
604
605@end
606