app_list_service_mac.mm revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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#import "chrome/browser/ui/app_list/app_list_service_mac.h" 6 7#include <ApplicationServices/ApplicationServices.h> 8#import <Cocoa/Cocoa.h> 9 10#include "apps/app_launcher.h" 11#include "apps/app_shim/app_shim_mac.h" 12#include "apps/pref_names.h" 13#include "base/bind.h" 14#include "base/command_line.h" 15#include "base/file_util.h" 16#include "base/lazy_instance.h" 17#include "base/memory/singleton.h" 18#include "base/message_loop/message_loop.h" 19#include "chrome/browser/browser_process.h" 20#include "chrome/browser/extensions/extension_service.h" 21#include "chrome/browser/extensions/extension_system.h" 22#include "chrome/browser/profiles/profile_manager.h" 23#include "chrome/browser/ui/app_list/app_list_controller_delegate.h" 24#include "chrome/browser/ui/app_list/app_list_service.h" 25#include "chrome/browser/ui/app_list/app_list_service_impl.h" 26#include "chrome/browser/ui/app_list/app_list_view_delegate.h" 27#include "chrome/browser/ui/browser_commands.h" 28#include "chrome/browser/ui/extensions/application_launch.h" 29#include "chrome/browser/ui/web_applications/web_app_ui.h" 30#include "chrome/browser/web_applications/web_app.h" 31#include "chrome/browser/web_applications/web_app_mac.h" 32#include "chrome/common/chrome_version_info.h" 33#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" 34#include "chrome/common/mac/app_mode_common.h" 35#include "content/public/browser/browser_thread.h" 36#include "grit/chrome_unscaled_resources.h" 37#include "grit/google_chrome_strings.h" 38#include "net/base/url_util.h" 39#import "ui/app_list/cocoa/app_list_view_controller.h" 40#import "ui/app_list/cocoa/app_list_window_controller.h" 41#include "ui/app_list/search_box_model.h" 42#include "ui/base/l10n/l10n_util.h" 43#include "ui/base/resource/resource_bundle.h" 44#include "ui/gfx/display.h" 45#include "ui/gfx/screen.h" 46 47namespace gfx { 48class ImageSkia; 49} 50 51// Controller for animations that show or hide the app list. 52@interface AppListAnimationController : NSObject<NSAnimationDelegate> { 53 @private 54 // When closing, the window to close. Retained until the animation ends. 55 base::scoped_nsobject<NSWindow> window_; 56 // The animation started and owned by |self|. Reset when the animation ends. 57 base::scoped_nsobject<NSViewAnimation> animation_; 58} 59 60// Returns whether |window_| is scheduled to be closed when the animation ends. 61- (BOOL)isClosing; 62 63// Animate |window| to show or close it, after cancelling any current animation. 64// Translates from the current location to |targetOrigin| and fades in or out. 65- (void)animateWindow:(NSWindow*)window 66 targetOrigin:(NSPoint)targetOrigin 67 closing:(BOOL)closing; 68 69@end 70 71namespace { 72 73// Version of the app list shortcut version installed. 74const int kShortcutVersion = 1; 75 76// Duration of show and hide animations. 77const NSTimeInterval kAnimationDuration = 0.2; 78 79// Distance towards the screen edge that the app list moves from when showing. 80const CGFloat kDistanceMovedOnShow = 20; 81 82class AppListControllerDelegateCocoa : public AppListControllerDelegate { 83 public: 84 AppListControllerDelegateCocoa(); 85 virtual ~AppListControllerDelegateCocoa(); 86 87 private: 88 // AppListControllerDelegate overrides: 89 virtual void DismissView() OVERRIDE; 90 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE; 91 virtual bool CanPin() OVERRIDE; 92 virtual bool CanDoCreateShortcutsFlow(bool is_platform_app) OVERRIDE; 93 virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE; 94 virtual void DoCreateShortcutsFlow(Profile* profile, 95 const std::string& extension_id) OVERRIDE; 96 virtual void ActivateApp(Profile* profile, 97 const extensions::Extension* extension, 98 AppListSource source, 99 int event_flags) OVERRIDE; 100 virtual void LaunchApp(Profile* profile, 101 const extensions::Extension* extension, 102 AppListSource source, 103 int event_flags) OVERRIDE; 104 virtual void ShowForProfileByPath( 105 const base::FilePath& profile_path) OVERRIDE; 106 107 DISALLOW_COPY_AND_ASSIGN(AppListControllerDelegateCocoa); 108}; 109 110ShellIntegration::ShortcutInfo GetAppListShortcutInfo( 111 const base::FilePath& profile_path) { 112 ShellIntegration::ShortcutInfo shortcut_info; 113 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 114 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 115 shortcut_info.title = 116 l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME_CANARY); 117 } else { 118 shortcut_info.title = l10n_util::GetStringUTF16(IDS_APP_LIST_SHORTCUT_NAME); 119 } 120 121 shortcut_info.extension_id = app_mode::kAppListModeId; 122 shortcut_info.description = shortcut_info.title; 123 shortcut_info.profile_path = profile_path; 124 125 return shortcut_info; 126} 127 128void CreateAppListShim(const base::FilePath& profile_path) { 129 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 130 WebApplicationInfo web_app_info; 131 ShellIntegration::ShortcutInfo shortcut_info = 132 GetAppListShortcutInfo(profile_path); 133 134 ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance(); 135 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 136 if (channel == chrome::VersionInfo::CHANNEL_CANARY) { 137#if defined(GOOGLE_CHROME_BUILD) 138 shortcut_info.favicon.Add( 139 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_16)); 140 shortcut_info.favicon.Add( 141 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_32)); 142 shortcut_info.favicon.Add( 143 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_128)); 144 shortcut_info.favicon.Add( 145 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_CANARY_256)); 146#else 147 NOTREACHED(); 148#endif 149 } else { 150 shortcut_info.favicon.Add( 151 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_16)); 152 shortcut_info.favicon.Add( 153 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_32)); 154 shortcut_info.favicon.Add( 155 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_128)); 156 shortcut_info.favicon.Add( 157 *resource_bundle.GetImageSkiaNamed(IDR_APP_LIST_256)); 158 } 159 160 ShellIntegration::ShortcutLocations shortcut_locations; 161 PrefService* local_state = g_browser_process->local_state(); 162 int installed_version = 163 local_state->GetInteger(apps::prefs::kAppLauncherShortcutVersion); 164 165 // If this is a first-time install, add a dock icon. Otherwise just update 166 // the target, and wait for OSX to refresh its icon caches. This might not 167 // occur until a reboot, but OSX does not offer a nicer way. Deleting cache 168 // files on disk and killing processes can easily result in icon corruption. 169 if (installed_version == 0) 170 shortcut_locations.in_quick_launch_bar = true; 171 172 web_app::CreateShortcuts(shortcut_info, 173 shortcut_locations, 174 web_app::SHORTCUT_CREATION_AUTOMATED); 175 176 local_state->SetInteger(apps::prefs::kAppLauncherShortcutVersion, 177 kShortcutVersion); 178} 179 180void CreateShortcutsInDefaultLocation( 181 const ShellIntegration::ShortcutInfo& shortcut_info) { 182 web_app::CreateShortcuts(shortcut_info, 183 ShellIntegration::ShortcutLocations(), 184 web_app::SHORTCUT_CREATION_BY_USER); 185} 186 187NSRunningApplication* ActiveApplicationNotChrome() { 188 NSArray* applications = [[NSWorkspace sharedWorkspace] runningApplications]; 189 for (NSRunningApplication* application in applications) { 190 if (![application isActive]) 191 continue; 192 193 if ([application isEqual:[NSRunningApplication currentApplication]]) 194 return nil; // Chrome is active. 195 196 return application; 197 } 198 199 return nil; 200} 201 202AppListControllerDelegateCocoa::AppListControllerDelegateCocoa() {} 203 204AppListControllerDelegateCocoa::~AppListControllerDelegateCocoa() {} 205 206void AppListControllerDelegateCocoa::DismissView() { 207 AppListServiceMac::GetInstance()->DismissAppList(); 208} 209 210gfx::NativeWindow AppListControllerDelegateCocoa::GetAppListWindow() { 211 return AppListServiceMac::GetInstance()->GetAppListWindow(); 212} 213 214bool AppListControllerDelegateCocoa::CanPin() { 215 return false; 216} 217 218bool AppListControllerDelegateCocoa::CanDoCreateShortcutsFlow( 219 bool is_platform_app) { 220 return false; 221} 222 223void AppListControllerDelegateCocoa::DoCreateShortcutsFlow( 224 Profile* profile, const std::string& extension_id) { 225 ExtensionService* service = 226 extensions::ExtensionSystem::Get(profile)->extension_service(); 227 DCHECK(service); 228 const extensions::Extension* extension = 229 service->GetInstalledExtension(extension_id); 230 DCHECK(extension); 231 232 web_app::UpdateShortcutInfoAndIconForApp( 233 *extension, profile, base::Bind(&CreateShortcutsInDefaultLocation)); 234} 235 236void AppListControllerDelegateCocoa::CreateNewWindow( 237 Profile* profile, bool incognito) { 238 Profile* window_profile = incognito ? 239 profile->GetOffTheRecordProfile() : profile; 240 chrome::NewEmptyWindow(window_profile, chrome::GetActiveDesktop()); 241} 242 243void AppListControllerDelegateCocoa::ActivateApp( 244 Profile* profile, 245 const extensions::Extension* extension, 246 AppListSource source, 247 int event_flags) { 248 LaunchApp(profile, extension, source, event_flags); 249} 250 251void AppListControllerDelegateCocoa::LaunchApp( 252 Profile* profile, 253 const extensions::Extension* extension, 254 AppListSource source, 255 int event_flags) { 256 AppListServiceImpl::RecordAppListAppLaunch(); 257 258 chrome::AppLaunchParams params( 259 profile, extension, NEW_FOREGROUND_TAB); 260 261 if (source != LAUNCH_FROM_UNKNOWN && 262 extension->id() == extension_misc::kWebStoreAppId) { 263 // Set an override URL to include the source. 264 GURL extension_url = extensions::AppLaunchInfo::GetFullLaunchURL(extension); 265 params.override_url = net::AppendQueryParameter( 266 extension_url, 267 extension_urls::kWebstoreSourceField, 268 AppListSourceToString(source)); 269 } 270 271 chrome::OpenApplication(params); 272} 273 274void AppListControllerDelegateCocoa::ShowForProfileByPath( 275 const base::FilePath& profile_path) { 276 AppListService* service = AppListServiceMac::GetInstance(); 277 service->SetProfilePath(profile_path); 278 service->Show(); 279} 280 281enum DockLocation { 282 DockLocationOtherDisplay, 283 DockLocationBottom, 284 DockLocationLeft, 285 DockLocationRight, 286}; 287 288DockLocation DockLocationInDisplay(const gfx::Display& display) { 289 // Assume the dock occupies part of the work area either on the left, right or 290 // bottom of the display. Note in the autohide case, it is always 4 pixels. 291 const gfx::Rect work_area = display.work_area(); 292 const gfx::Rect display_bounds = display.bounds(); 293 if (work_area.bottom() != display_bounds.bottom()) 294 return DockLocationBottom; 295 296 if (work_area.x() != display_bounds.x()) 297 return DockLocationLeft; 298 299 if (work_area.right() != display_bounds.right()) 300 return DockLocationRight; 301 302 return DockLocationOtherDisplay; 303} 304 305// If |work_area_edge| is too close to the |screen_edge| (e.g. autohide dock), 306// adjust |anchor| away from the edge by a constant amount to reduce overlap and 307// ensure the dock icon can still be clicked to dismiss the app list. 308int AdjustPointForDynamicDock(int anchor, int screen_edge, int work_area_edge) { 309 const int kAutohideDockThreshold = 10; 310 const int kExtraDistance = 50; // A dock with 40 items is about this size. 311 if (abs(work_area_edge - screen_edge) > kAutohideDockThreshold) 312 return anchor; 313 314 return anchor + 315 (screen_edge < work_area_edge ? kExtraDistance : -kExtraDistance); 316} 317 318void GetAppListWindowOrigins( 319 NSWindow* window, NSPoint* target_origin, NSPoint* start_origin) { 320 gfx::Screen* const screen = gfx::Screen::GetScreenFor([window contentView]); 321 // Ensure y coordinates are flipped back into AppKit's coordinate system. 322 const CGFloat max_y = NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]); 323 if (!CGCursorIsVisible()) { 324 // If Chrome is the active application, display on the same display as 325 // Chrome's keyWindow since this will catch activations triggered, e.g, via 326 // WebStore install. If another application is active, OSX doesn't provide a 327 // reliable way to get the display in use. Fall back to the primary display 328 // since it has the menu bar and is likely to be correct, e.g., for 329 // activations from Spotlight. 330 const gfx::NativeView key_view = [[NSApp keyWindow] contentView]; 331 const gfx::Rect work_area = key_view && [NSApp isActive] ? 332 screen->GetDisplayNearestWindow(key_view).work_area() : 333 screen->GetPrimaryDisplay().work_area(); 334 *target_origin = NSMakePoint(work_area.x(), max_y - work_area.bottom()); 335 *start_origin = *target_origin; 336 return; 337 } 338 339 gfx::Point anchor = screen->GetCursorScreenPoint(); 340 const gfx::Display display = screen->GetDisplayNearestPoint(anchor); 341 const DockLocation dock_location = DockLocationInDisplay(display); 342 const gfx::Rect display_bounds = display.bounds(); 343 344 if (dock_location == DockLocationOtherDisplay) { 345 // Just display at the bottom-left of the display the cursor is on. 346 *target_origin = NSMakePoint(display_bounds.x(), 347 max_y - display_bounds.bottom()); 348 *start_origin = *target_origin; 349 return; 350 } 351 352 // Anchor the center of the window in a region that prevents the window 353 // showing outside of the work area. 354 const NSSize window_size = [window frame].size; 355 const gfx::Rect work_area = display.work_area(); 356 gfx::Rect anchor_area = work_area; 357 anchor_area.Inset(window_size.width / 2, window_size.height / 2); 358 anchor.SetToMax(anchor_area.origin()); 359 anchor.SetToMin(anchor_area.bottom_right()); 360 361 // Move anchor to the dock, keeping the other axis aligned with the cursor. 362 switch (dock_location) { 363 case DockLocationBottom: 364 anchor.set_y(AdjustPointForDynamicDock( 365 anchor_area.bottom(), display_bounds.bottom(), work_area.bottom())); 366 break; 367 case DockLocationLeft: 368 anchor.set_x(AdjustPointForDynamicDock( 369 anchor_area.x(), display_bounds.x(), work_area.x())); 370 break; 371 case DockLocationRight: 372 anchor.set_x(AdjustPointForDynamicDock( 373 anchor_area.right(), display_bounds.right(), work_area.right())); 374 break; 375 default: 376 NOTREACHED(); 377 } 378 379 *target_origin = NSMakePoint(anchor.x() - window_size.width / 2, 380 max_y - anchor.y() - window_size.height / 2); 381 *start_origin = *target_origin; 382 383 switch (dock_location) { 384 case DockLocationBottom: 385 start_origin->y -= kDistanceMovedOnShow; 386 break; 387 case DockLocationLeft: 388 start_origin->x -= kDistanceMovedOnShow; 389 break; 390 case DockLocationRight: 391 start_origin->x += kDistanceMovedOnShow; 392 break; 393 default: 394 NOTREACHED(); 395 } 396} 397 398} // namespace 399 400AppListServiceMac::AppListServiceMac() { 401 animation_controller_.reset([[AppListAnimationController alloc] init]); 402} 403 404AppListServiceMac::~AppListServiceMac() {} 405 406// static 407AppListServiceMac* AppListServiceMac::GetInstance() { 408 return Singleton<AppListServiceMac, 409 LeakySingletonTraits<AppListServiceMac> >::get(); 410} 411 412void AppListServiceMac::Init(Profile* initial_profile) { 413 // On Mac, Init() is called multiple times for a process: any time there is no 414 // browser window open and a new window is opened, and during process startup 415 // to handle the silent launch case (e.g. for app shims). In the startup case, 416 // a profile has not yet been determined so |initial_profile| will be NULL. 417 static bool init_called_with_profile = false; 418 if (initial_profile && !init_called_with_profile) { 419 init_called_with_profile = true; 420 HandleCommandLineFlags(initial_profile); 421 PrefService* local_state = g_browser_process->local_state(); 422 if (!apps::IsAppLauncherEnabled()) { 423 local_state->SetInteger(apps::prefs::kAppLauncherShortcutVersion, 0); 424 } else { 425 int installed_shortcut_version = 426 local_state->GetInteger(apps::prefs::kAppLauncherShortcutVersion); 427 428 if (kShortcutVersion > installed_shortcut_version) 429 CreateShortcut(); 430 } 431 } 432 433 static bool init_called = false; 434 if (init_called) 435 return; 436 437 init_called = true; 438 apps::AppShimHandler::RegisterHandler(app_mode::kAppListModeId, 439 AppListServiceMac::GetInstance()); 440} 441 442void AppListServiceMac::CreateForProfile(Profile* requested_profile) { 443 if (profile() == requested_profile) 444 return; 445 446 SetProfile(requested_profile); 447 448 if (window_controller_) { 449 // Clear the search box. 450 [[window_controller_ appListViewController] searchBoxModel] 451 ->SetText(base::string16()); 452 } else { 453 window_controller_.reset([[AppListWindowController alloc] init]); 454 } 455 456 scoped_ptr<app_list::AppListViewDelegate> delegate( 457 new AppListViewDelegate(new AppListControllerDelegateCocoa(), profile())); 458 [[window_controller_ appListViewController] setDelegate:delegate.Pass()]; 459} 460 461void AppListServiceMac::ShowForProfile(Profile* requested_profile) { 462 if (requested_profile->IsManaged()) 463 return; 464 465 InvalidatePendingProfileLoads(); 466 467 if (requested_profile == profile()) { 468 ShowWindowNearDock(); 469 return; 470 } 471 472 SetProfilePath(requested_profile->GetPath()); 473 CreateForProfile(requested_profile); 474 ShowWindowNearDock(); 475} 476 477void AppListServiceMac::DismissAppList() { 478 if (!IsAppListVisible()) 479 return; 480 481 // If the app list is currently the main window, it will activate the next 482 // Chrome window when dismissed. But if a different application was active 483 // when the app list was shown, activate that instead. 484 base::scoped_nsobject<NSRunningApplication> prior_app; 485 if ([[window_controller_ window] isMainWindow]) 486 prior_app.swap(previously_active_application_); 487 else 488 previously_active_application_.reset(); 489 490 // If activation is successful, the app list will lose main status and try to 491 // close itself again. It can't be closed in this runloop iteration without 492 // OSX deciding to raise the next Chrome window, and _then_ activating the 493 // application on top. This also occurs if no activation option is given. 494 if ([prior_app activateWithOptions:NSApplicationActivateIgnoringOtherApps]) 495 return; 496 497 [animation_controller_ animateWindow:[window_controller_ window] 498 targetOrigin:last_start_origin_ 499 closing:YES]; 500} 501 502bool AppListServiceMac::IsAppListVisible() const { 503 return [[window_controller_ window] isVisible] && 504 ![animation_controller_ isClosing]; 505} 506 507void AppListServiceMac::CreateShortcut() { 508 CreateAppListShim(GetProfilePath( 509 g_browser_process->profile_manager()->user_data_dir())); 510} 511 512NSWindow* AppListServiceMac::GetAppListWindow() { 513 return [window_controller_ window]; 514} 515 516AppListControllerDelegate* AppListServiceMac::CreateControllerDelegate() { 517 return new AppListControllerDelegateCocoa(); 518} 519 520void AppListServiceMac::OnShimLaunch(apps::AppShimHandler::Host* host, 521 apps::AppShimLaunchType launch_type, 522 const std::vector<base::FilePath>& files) { 523 if (IsAppListVisible()) 524 DismissAppList(); 525 else 526 Show(); 527 528 // Always close the shim process immediately. 529 host->OnAppLaunchComplete(apps::APP_SHIM_LAUNCH_DUPLICATE_HOST); 530} 531 532void AppListServiceMac::OnShimClose(apps::AppShimHandler::Host* host) {} 533 534void AppListServiceMac::OnShimFocus(apps::AppShimHandler::Host* host, 535 apps::AppShimFocusType focus_type, 536 const std::vector<base::FilePath>& files) {} 537 538void AppListServiceMac::OnShimSetHidden(apps::AppShimHandler::Host* host, 539 bool hidden) {} 540 541void AppListServiceMac::OnShimQuit(apps::AppShimHandler::Host* host) {} 542 543void AppListServiceMac::ShowWindowNearDock() { 544 if (IsAppListVisible()) 545 return; 546 547 NSWindow* window = GetAppListWindow(); 548 DCHECK(window); 549 NSPoint target_origin; 550 GetAppListWindowOrigins(window, &target_origin, &last_start_origin_); 551 [window setFrameOrigin:last_start_origin_]; 552 553 // Before activating, see if an application other than Chrome is currently the 554 // active application, so that it can be reactivated when dismissing. 555 previously_active_application_.reset([ActiveApplicationNotChrome() retain]); 556 557 [animation_controller_ animateWindow:[window_controller_ window] 558 targetOrigin:target_origin 559 closing:NO]; 560 [window makeKeyAndOrderFront:nil]; 561 [NSApp activateIgnoringOtherApps:YES]; 562 RecordAppListLaunch(); 563} 564 565// static 566AppListService* AppListService::Get() { 567 return AppListServiceMac::GetInstance(); 568} 569 570// static 571void AppListService::InitAll(Profile* initial_profile) { 572 Get()->Init(initial_profile); 573} 574 575@implementation AppListAnimationController 576 577- (BOOL)isClosing { 578 return !!window_; 579} 580 581- (void)animateWindow:(NSWindow*)window 582 targetOrigin:(NSPoint)targetOrigin 583 closing:(BOOL)closing { 584 // First, stop the existing animation, if there is one. 585 [animation_ stopAnimation]; 586 587 NSRect targetFrame = [window frame]; 588 targetFrame.origin = targetOrigin; 589 590 // NSViewAnimation has a quirk when setting the curve to NSAnimationEaseOut 591 // where it attempts to auto-reverse the animation. FadeOut becomes FadeIn 592 // (good), but FrameKey is also switched (bad). So |targetFrame| needs to be 593 // put on the StartFrameKey when using NSAnimationEaseOut for showing. 594 NSArray* animationArray = @[ 595 @{ 596 NSViewAnimationTargetKey : window, 597 NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect, 598 (closing ? NSViewAnimationEndFrameKey : NSViewAnimationStartFrameKey) : 599 [NSValue valueWithRect:targetFrame] 600 } 601 ]; 602 animation_.reset( 603 [[NSViewAnimation alloc] initWithViewAnimations:animationArray]); 604 [animation_ setDuration:kAnimationDuration]; 605 [animation_ setDelegate:self]; 606 607 if (closing) { 608 [animation_ setAnimationCurve:NSAnimationEaseIn]; 609 window_.reset([window retain]); 610 } else { 611 [window setAlphaValue:0.0f]; 612 [animation_ setAnimationCurve:NSAnimationEaseOut]; 613 window_.reset(); 614 } 615 [animation_ startAnimation]; 616} 617 618- (void)animationDidEnd:(NSAnimation*)animation { 619 [window_ close]; 620 window_.reset(); 621 animation_.reset(); 622} 623 624@end 625