OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_impl.h" | 5 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_impl.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 | 8 |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
83 #include "ui/aura/window_event_dispatcher.h" | 83 #include "ui/aura/window_event_dispatcher.h" |
84 #include "ui/base/l10n/l10n_util.h" | 84 #include "ui/base/l10n/l10n_util.h" |
85 #include "ui/base/resource/resource_bundle.h" | 85 #include "ui/base/resource/resource_bundle.h" |
86 #include "ui/base/window_open_disposition.h" | 86 #include "ui/base/window_open_disposition.h" |
87 #include "ui/display/types/display_constants.h" | 87 #include "ui/display/types/display_constants.h" |
88 #include "ui/keyboard/keyboard_util.h" | 88 #include "ui/keyboard/keyboard_util.h" |
89 #include "ui/resources/grit/ui_resources.h" | 89 #include "ui/resources/grit/ui_resources.h" |
90 #include "ui/wm/core/window_animations.h" | 90 #include "ui/wm/core/window_animations.h" |
91 | 91 |
92 using extensions::Extension; | 92 using extensions::Extension; |
93 using extension_misc::kChromeAppId; | |
94 using extension_misc::kGmailAppId; | 93 using extension_misc::kGmailAppId; |
95 using content::WebContents; | 94 using content::WebContents; |
96 | 95 |
97 namespace { | 96 namespace { |
98 | 97 |
99 int64_t GetDisplayIDForShelf(ash::WmShelf* shelf) { | 98 int64_t GetDisplayIDForShelf(ash::WmShelf* shelf) { |
100 display::Display display = | 99 display::Display display = |
101 shelf->GetWindow()->GetRootWindow()->GetDisplayNearestWindow(); | 100 shelf->GetWindow()->GetRootWindow()->GetDisplayNearestWindow(); |
102 DCHECK(display.is_valid()); | 101 DCHECK(display.is_valid()); |
103 return display.id(); | 102 return display.id(); |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 ChromeLauncherControllerImpl::~ChromeLauncherControllerImpl() { | 267 ChromeLauncherControllerImpl::~ChromeLauncherControllerImpl() { |
269 // Reset the BrowserStatusMonitor as it has a weak pointer to this. | 268 // Reset the BrowserStatusMonitor as it has a weak pointer to this. |
270 browser_status_monitor_.reset(); | 269 browser_status_monitor_.reset(); |
271 | 270 |
272 // Reset the app window controllers here since it has a weak pointer to this. | 271 // Reset the app window controllers here since it has a weak pointer to this. |
273 app_window_controllers_.clear(); | 272 app_window_controllers_.clear(); |
274 | 273 |
275 model_->RemoveObserver(this); | 274 model_->RemoveObserver(this); |
276 if (ash::Shell::HasInstance()) | 275 if (ash::Shell::HasInstance()) |
277 ash::Shell::Get()->window_tree_host_manager()->RemoveObserver(this); | 276 ash::Shell::Get()->window_tree_host_manager()->RemoveObserver(this); |
| 277 for (const auto& pair : id_to_item_controller_map_) { |
| 278 int index = model_->ItemIndexByID(pair.first); |
| 279 // A "browser proxy" is not known to the model and this removal does |
| 280 // therefore not need to be propagated to the model. |
| 281 if (index != -1 && |
| 282 model_->items()[index].type != ash::TYPE_BROWSER_SHORTCUT) |
| 283 model_->RemoveItemAt(index); |
| 284 } |
278 | 285 |
279 // Release all profile dependent resources. | 286 // Release all profile dependent resources. |
280 ReleaseProfile(); | 287 ReleaseProfile(); |
281 | 288 |
282 // Get rid of the multi user window manager instance. | 289 // Get rid of the multi user window manager instance. |
283 chrome::MultiUserWindowManager::DeleteInstance(); | 290 chrome::MultiUserWindowManager::DeleteInstance(); |
284 } | 291 } |
285 | 292 |
286 ash::ShelfID ChromeLauncherControllerImpl::CreateAppLauncherItem( | 293 ash::ShelfID ChromeLauncherControllerImpl::CreateAppLauncherItem( |
287 std::unique_ptr<ash::ShelfItemDelegate> item_delegate, | 294 std::unique_ptr<ash::ShelfItemDelegate> item_delegate, |
(...skipping 23 matching lines...) Expand all Loading... |
311 void ChromeLauncherControllerImpl::SetItemStatus(ash::ShelfID id, | 318 void ChromeLauncherControllerImpl::SetItemStatus(ash::ShelfID id, |
312 ash::ShelfItemStatus status) { | 319 ash::ShelfItemStatus status) { |
313 const ash::ShelfItem* item = GetItem(id); | 320 const ash::ShelfItem* item = GetItem(id); |
314 if (item && item->status != status) { | 321 if (item && item->status != status) { |
315 ash::ShelfItem new_item = *item; | 322 ash::ShelfItem new_item = *item; |
316 new_item.status = status; | 323 new_item.status = status; |
317 model_->Set(model_->ItemIndexByID(id), new_item); | 324 model_->Set(model_->ItemIndexByID(id), new_item); |
318 } | 325 } |
319 } | 326 } |
320 | 327 |
| 328 void ChromeLauncherControllerImpl::SetShelfItemDelegate( |
| 329 ash::ShelfID id, |
| 330 std::unique_ptr<ash::ShelfItemDelegate> item_delegate) { |
| 331 CHECK(item_delegate); |
| 332 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); |
| 333 CHECK(iter != id_to_item_controller_map_.end()); |
| 334 item_delegate->set_shelf_id(id); |
| 335 iter->second = item_delegate.get(); |
| 336 model_->SetShelfItemDelegate(id, std::move(item_delegate)); |
| 337 } |
| 338 |
321 void ChromeLauncherControllerImpl::CloseLauncherItem(ash::ShelfID id) { | 339 void ChromeLauncherControllerImpl::CloseLauncherItem(ash::ShelfID id) { |
322 CHECK(id); | 340 CHECK(id); |
323 if (IsPinned(id)) { | 341 if (IsPinned(id)) { |
324 // Create a new shortcut delegate. | 342 // Create a new shortcut delegate. |
325 SetItemStatus(id, ash::STATUS_CLOSED); | 343 SetItemStatus(id, ash::STATUS_CLOSED); |
326 model_->SetShelfItemDelegate(id, AppShortcutLauncherItemController::Create( | 344 SetShelfItemDelegate(id, AppShortcutLauncherItemController::Create( |
327 GetItem(id)->app_launch_id)); | 345 GetItem(id)->app_launch_id)); |
328 } else { | 346 } else { |
329 RemoveShelfItem(id); | 347 LauncherItemClosed(id); |
330 } | 348 } |
331 } | 349 } |
332 | 350 |
333 void ChromeLauncherControllerImpl::UnpinShelfItemInternal(ash::ShelfID id) { | 351 void ChromeLauncherControllerImpl::UnpinShelfItemInternal(ash::ShelfID id) { |
334 const ash::ShelfItem* item = GetItem(id); | 352 const ash::ShelfItem* item = GetItem(id); |
335 if (item && item->status != ash::STATUS_CLOSED) | 353 if (item && item->status != ash::STATUS_CLOSED) |
336 UnpinRunningAppInternal(model_->ItemIndexByID(id)); | 354 UnpinRunningAppInternal(model_->ItemIndexByID(id)); |
337 else | 355 else |
338 RemoveShelfItem(id); | 356 LauncherItemClosed(id); |
339 } | 357 } |
340 | 358 |
341 bool ChromeLauncherControllerImpl::IsPinned(ash::ShelfID id) { | 359 bool ChromeLauncherControllerImpl::IsPinned(ash::ShelfID id) { |
342 const ash::ShelfItem* item = GetItem(id); | 360 const ash::ShelfItem* item = GetItem(id); |
343 return item && ItemTypeIsPinned(*item); | 361 return item && ItemTypeIsPinned(*item); |
344 } | 362 } |
345 | 363 |
346 void ChromeLauncherControllerImpl::SetV1AppStatus(const std::string& app_id, | 364 void ChromeLauncherControllerImpl::SetV1AppStatus(const std::string& app_id, |
347 ash::ShelfItemStatus status) { | 365 ash::ShelfItemStatus status) { |
348 ash::ShelfID id = GetShelfIDForAppID(app_id); | 366 ash::ShelfID id = GetShelfIDForAppID(app_id); |
349 const ash::ShelfItem* item = GetItem(id); | 367 const ash::ShelfItem* item = GetItem(id); |
350 if (item) { | 368 if (item) { |
351 if (!IsPinned(id) && status == ash::STATUS_CLOSED) | 369 if (!IsPinned(id) && status == ash::STATUS_CLOSED) |
352 RemoveShelfItem(id); | 370 LauncherItemClosed(id); |
353 else | 371 else |
354 SetItemStatus(id, status); | 372 SetItemStatus(id, status); |
355 } else if (status != ash::STATUS_CLOSED && !app_id.empty()) { | 373 } else if (status != ash::STATUS_CLOSED && !app_id.empty()) { |
356 InsertAppLauncherItem( | 374 InsertAppLauncherItem( |
357 AppShortcutLauncherItemController::Create(ash::AppLaunchId(app_id)), | 375 AppShortcutLauncherItemController::Create(ash::AppLaunchId(app_id)), |
358 status, model_->item_count(), ash::TYPE_APP); | 376 status, model_->item_count(), ash::TYPE_APP); |
359 } | 377 } |
360 } | 378 } |
361 | 379 |
362 void ChromeLauncherControllerImpl::Launch(ash::ShelfID id, int event_flags) { | 380 void ChromeLauncherControllerImpl::Launch(ash::ShelfID id, int event_flags) { |
363 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); | 381 ash::ShelfItemDelegate* delegate = GetShelfItemDelegate(id); |
364 if (!delegate) | 382 if (!delegate) |
365 return; // In case invoked from menu and item closed while menu up. | 383 return; // In case invoked from menu and item closed while menu up. |
366 | 384 |
367 // Launching some items replaces the associated item delegate instance, | 385 // Launching some items replaces the associated item delegate instance, |
368 // which destroys the app and launch id strings; making copies avoid crashes. | 386 // which destroys the app and launch id strings; making copies avoid crashes. |
369 LaunchApp(ash::AppLaunchId(delegate->app_id(), delegate->launch_id()), | 387 LaunchApp(ash::AppLaunchId(delegate->app_id(), delegate->launch_id()), |
370 ash::LAUNCH_FROM_UNKNOWN, event_flags); | 388 ash::LAUNCH_FROM_UNKNOWN, event_flags); |
371 } | 389 } |
372 | 390 |
373 void ChromeLauncherControllerImpl::Close(ash::ShelfID id) { | 391 void ChromeLauncherControllerImpl::Close(ash::ShelfID id) { |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
456 if (id) { | 474 if (id) { |
457 SetItemStatus(id, (app_state == APP_STATE_WINDOW_ACTIVE || | 475 SetItemStatus(id, (app_state == APP_STATE_WINDOW_ACTIVE || |
458 app_state == APP_STATE_ACTIVE) | 476 app_state == APP_STATE_ACTIVE) |
459 ? ash::STATUS_ACTIVE | 477 ? ash::STATUS_ACTIVE |
460 : GetAppState(app_id)); | 478 : GetAppState(app_id)); |
461 } | 479 } |
462 } | 480 } |
463 | 481 |
464 ash::ShelfID ChromeLauncherControllerImpl::GetShelfIDForWebContents( | 482 ash::ShelfID ChromeLauncherControllerImpl::GetShelfIDForWebContents( |
465 content::WebContents* contents) { | 483 content::WebContents* contents) { |
| 484 DCHECK(contents); |
| 485 |
466 std::string app_id = launcher_controller_helper()->GetAppID(contents); | 486 std::string app_id = launcher_controller_helper()->GetAppID(contents); |
| 487 |
467 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) | 488 if (app_id.empty() && ContentCanBeHandledByGmailApp(contents)) |
468 app_id = kGmailAppId; | 489 app_id = kGmailAppId; |
469 | 490 |
470 ash::ShelfID id = GetShelfIDForAppID(app_id); | 491 ash::ShelfID id = GetShelfIDForAppID(app_id); |
471 // If there is no dedicated app item, use the browser shortcut item. | 492 |
472 return id == ash::kInvalidShelfID ? GetShelfIDForAppID(kChromeAppId) : id; | 493 if (app_id.empty() || !id) { |
| 494 int browser_index = model_->GetItemIndexForType(ash::TYPE_BROWSER_SHORTCUT); |
| 495 return model_->items()[browser_index].id; |
| 496 } |
| 497 |
| 498 return id; |
473 } | 499 } |
474 | 500 |
475 void ChromeLauncherControllerImpl::SetRefocusURLPatternForTest( | 501 void ChromeLauncherControllerImpl::SetRefocusURLPatternForTest( |
476 ash::ShelfID id, | 502 ash::ShelfID id, |
477 const GURL& url) { | 503 const GURL& url) { |
478 const ash::ShelfItem* item = GetItem(id); | 504 const ash::ShelfItem* item = GetItem(id); |
479 if (item && !IsPlatformApp(id) && | 505 if (item && !IsPlatformApp(id) && |
480 (item->type == ash::TYPE_PINNED_APP || item->type == ash::TYPE_APP)) { | 506 (item->type == ash::TYPE_PINNED_APP || item->type == ash::TYPE_APP)) { |
481 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); | 507 ash::ShelfItemDelegate* item_delegate = GetShelfItemDelegate(id); |
482 AppShortcutLauncherItemController* item_controller = | 508 AppShortcutLauncherItemController* item_controller = |
483 static_cast<AppShortcutLauncherItemController*>(delegate); | 509 static_cast<AppShortcutLauncherItemController*>(item_delegate); |
484 item_controller->set_refocus_url(url); | 510 item_controller->set_refocus_url(url); |
485 } else { | 511 } else { |
486 NOTREACHED() << "Invalid launcher item or type"; | 512 NOTREACHED() << "Invalid launcher item or type"; |
487 } | 513 } |
488 } | 514 } |
489 | 515 |
490 ash::ShelfAction ChromeLauncherControllerImpl::ActivateWindowOrMinimizeIfActive( | 516 ash::ShelfAction ChromeLauncherControllerImpl::ActivateWindowOrMinimizeIfActive( |
491 ui::BaseWindow* window, | 517 ui::BaseWindow* window, |
492 bool allow_minimize) { | 518 bool allow_minimize) { |
493 // In separated desktop mode we might have to teleport a window back to the | 519 // In separated desktop mode we might have to teleport a window back to the |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
550 | 576 |
551 void ChromeLauncherControllerImpl::AdditionalUserAddedToSession( | 577 void ChromeLauncherControllerImpl::AdditionalUserAddedToSession( |
552 Profile* profile) { | 578 Profile* profile) { |
553 // Switch the running applications to the new user. | 579 // Switch the running applications to the new user. |
554 for (auto& controller : app_window_controllers_) | 580 for (auto& controller : app_window_controllers_) |
555 controller->AdditionalUserAddedToSession(profile); | 581 controller->AdditionalUserAddedToSession(profile); |
556 } | 582 } |
557 | 583 |
558 ash::MenuItemList ChromeLauncherControllerImpl::GetAppMenuItemsForTesting( | 584 ash::MenuItemList ChromeLauncherControllerImpl::GetAppMenuItemsForTesting( |
559 const ash::ShelfItem& item) { | 585 const ash::ShelfItem& item) { |
560 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item.id); | 586 ash::ShelfItemDelegate* item_delegate = GetShelfItemDelegate(item.id); |
561 return delegate ? delegate->GetAppMenuItems(ui::EF_NONE) | 587 return item_delegate ? item_delegate->GetAppMenuItems(ui::EF_NONE) |
562 : ash::MenuItemList(); | 588 : ash::MenuItemList(); |
563 } | 589 } |
564 | 590 |
565 std::vector<content::WebContents*> | 591 std::vector<content::WebContents*> |
566 ChromeLauncherControllerImpl::GetV1ApplicationsFromAppId( | 592 ChromeLauncherControllerImpl::GetV1ApplicationsFromAppId( |
567 const std::string& app_id) { | 593 const std::string& app_id) { |
568 const ash::ShelfItem* item = GetItem(GetShelfIDForAppID(app_id)); | 594 const ash::ShelfItem* item = GetItem(GetShelfIDForAppID(app_id)); |
569 // If there is no such item pinned to the launcher, no menu gets created. | 595 // If there is no such item pinned to the launcher, no menu gets created. |
570 if (!item || item->type != ash::TYPE_PINNED_APP) | 596 if (!item || item->type != ash::TYPE_PINNED_APP) |
571 return std::vector<content::WebContents*>(); | 597 return std::vector<content::WebContents*>(); |
572 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item->id); | 598 ash::ShelfItemDelegate* item_delegate = GetShelfItemDelegate(item->id); |
573 AppShortcutLauncherItemController* item_controller = | 599 AppShortcutLauncherItemController* item_controller = |
574 static_cast<AppShortcutLauncherItemController*>(delegate); | 600 static_cast<AppShortcutLauncherItemController*>(item_delegate); |
575 return item_controller->GetRunningApplications(); | 601 return item_controller->GetRunningApplications(); |
576 } | 602 } |
577 | 603 |
578 void ChromeLauncherControllerImpl::ActivateShellApp(const std::string& app_id, | 604 void ChromeLauncherControllerImpl::ActivateShellApp(const std::string& app_id, |
579 int window_index) { | 605 int window_index) { |
580 const ash::ShelfItem* item = GetItem(GetShelfIDForAppID(app_id)); | 606 const ash::ShelfItem* item = GetItem(GetShelfIDForAppID(app_id)); |
581 if (item && | 607 if (item && |
582 (item->type == ash::TYPE_APP || item->type == ash::TYPE_PINNED_APP)) { | 608 (item->type == ash::TYPE_APP || item->type == ash::TYPE_PINNED_APP)) { |
583 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item->id); | 609 ash::ShelfItemDelegate* item_delegate = GetShelfItemDelegate(item->id); |
584 AppWindowLauncherItemController* item_controller = | 610 AppWindowLauncherItemController* item_controller = |
585 delegate->AsAppWindowLauncherItemController(); | 611 item_delegate->AsAppWindowLauncherItemController(); |
586 item_controller->ActivateIndexedApp(window_index); | 612 item_controller->ActivateIndexedApp(window_index); |
587 } | 613 } |
588 } | 614 } |
589 | 615 |
590 bool ChromeLauncherControllerImpl::IsWebContentHandledByApplication( | 616 bool ChromeLauncherControllerImpl::IsWebContentHandledByApplication( |
591 content::WebContents* web_contents, | 617 content::WebContents* web_contents, |
592 const std::string& app_id) { | 618 const std::string& app_id) { |
593 if ((web_contents_to_app_id_.find(web_contents) != | 619 if ((web_contents_to_app_id_.find(web_contents) != |
594 web_contents_to_app_id_.end()) && | 620 web_contents_to_app_id_.end()) && |
595 (web_contents_to_app_id_[web_contents] == app_id)) | 621 (web_contents_to_app_id_[web_contents] == app_id)) |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
639 const extensions::Extension* extension = | 665 const extensions::Extension* extension = |
640 GetExtensionForAppID(app_id, profile()); | 666 GetExtensionForAppID(app_id, profile()); |
641 if (extension) | 667 if (extension) |
642 return base::UTF8ToUTF16(extension->name()); | 668 return base::UTF8ToUTF16(extension->name()); |
643 } | 669 } |
644 return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); | 670 return l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE); |
645 } | 671 } |
646 | 672 |
647 BrowserShortcutLauncherItemController* | 673 BrowserShortcutLauncherItemController* |
648 ChromeLauncherControllerImpl::GetBrowserShortcutLauncherItemController() { | 674 ChromeLauncherControllerImpl::GetBrowserShortcutLauncherItemController() { |
649 ash::ShelfID id = GetShelfIDForAppID(kChromeAppId); | 675 for (const auto& pair : id_to_item_controller_map_) { |
650 ash::mojom::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); | 676 const ash::ShelfItem* item = GetItem(pair.first); |
651 DCHECK(delegate) << "There should be always be a browser shortcut item."; | 677 if (item && item->type == ash::TYPE_BROWSER_SHORTCUT) |
652 return static_cast<BrowserShortcutLauncherItemController*>(delegate); | 678 return static_cast<BrowserShortcutLauncherItemController*>(pair.second); |
| 679 } |
| 680 NOTREACHED() |
| 681 << "There should be always be a BrowserShortcutLauncherItemController."; |
| 682 return nullptr; |
| 683 } |
| 684 |
| 685 ash::ShelfItemDelegate* ChromeLauncherControllerImpl::GetShelfItemDelegate( |
| 686 const ash::ShelfID id) { |
| 687 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); |
| 688 return iter == id_to_item_controller_map_.end() ? nullptr : iter->second; |
653 } | 689 } |
654 | 690 |
655 bool ChromeLauncherControllerImpl::ShelfBoundsChangesProbablyWithUser( | 691 bool ChromeLauncherControllerImpl::ShelfBoundsChangesProbablyWithUser( |
656 ash::WmShelf* shelf, | 692 ash::WmShelf* shelf, |
657 const AccountId& account_id) const { | 693 const AccountId& account_id) const { |
658 Profile* other_profile = multi_user_util::GetProfileFromAccountId(account_id); | 694 Profile* other_profile = multi_user_util::GetProfileFromAccountId(account_id); |
659 if (!other_profile || other_profile == profile()) | 695 if (!other_profile || other_profile == profile()) |
660 return false; | 696 return false; |
661 | 697 |
662 // Note: The Auto hide state from preferences is not the same as the actual | 698 // Note: The Auto hide state from preferences is not the same as the actual |
(...skipping 20 matching lines...) Expand all Loading... |
683 user_switch_observer_->OnUserProfileReadyToSwitch(profile); | 719 user_switch_observer_->OnUserProfileReadyToSwitch(profile); |
684 } | 720 } |
685 | 721 |
686 ArcAppDeferredLauncherController* | 722 ArcAppDeferredLauncherController* |
687 ChromeLauncherControllerImpl::GetArcDeferredLauncher() { | 723 ChromeLauncherControllerImpl::GetArcDeferredLauncher() { |
688 return arc_deferred_launcher_.get(); | 724 return arc_deferred_launcher_.get(); |
689 } | 725 } |
690 | 726 |
691 const std::string& ChromeLauncherControllerImpl::GetLaunchIDForShelfID( | 727 const std::string& ChromeLauncherControllerImpl::GetLaunchIDForShelfID( |
692 ash::ShelfID id) { | 728 ash::ShelfID id) { |
693 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(id); | 729 ash::ShelfItemDelegate* delegate = GetShelfItemDelegate(id); |
694 return delegate ? delegate->launch_id() : base::EmptyString(); | 730 return delegate ? delegate->launch_id() : base::EmptyString(); |
695 } | 731 } |
696 | 732 |
697 void ChromeLauncherControllerImpl::AttachProfile(Profile* profile_to_attach) { | 733 void ChromeLauncherControllerImpl::AttachProfile(Profile* profile_to_attach) { |
698 // The base class implementation updates the helper and app icon loaders. | 734 // The base class implementation updates the helper and app icon loaders. |
699 ChromeLauncherController::AttachProfile(profile_to_attach); | 735 ChromeLauncherController::AttachProfile(profile_to_attach); |
700 | 736 |
701 pref_change_registrar_.Init(profile()->GetPrefs()); | 737 pref_change_registrar_.Init(profile()->GetPrefs()); |
702 pref_change_registrar_.Add( | 738 pref_change_registrar_.Add( |
703 prefs::kPolicyPinnedLauncherApps, | 739 prefs::kPolicyPinnedLauncherApps, |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
755 // Get shelf id for |app_id| and an empty |launch_id|. | 791 // Get shelf id for |app_id| and an empty |launch_id|. |
756 return GetShelfIDForAppIDAndLaunchID(app_id, std::string()); | 792 return GetShelfIDForAppIDAndLaunchID(app_id, std::string()); |
757 } | 793 } |
758 | 794 |
759 ash::ShelfID ChromeLauncherControllerImpl::GetShelfIDForAppIDAndLaunchID( | 795 ash::ShelfID ChromeLauncherControllerImpl::GetShelfIDForAppIDAndLaunchID( |
760 const std::string& app_id, | 796 const std::string& app_id, |
761 const std::string& launch_id) { | 797 const std::string& launch_id) { |
762 // TODO(khmel): Fix this Arc application id mapping. See http://b/31703859 | 798 // TODO(khmel): Fix this Arc application id mapping. See http://b/31703859 |
763 const std::string shelf_app_id = | 799 const std::string shelf_app_id = |
764 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId(app_id); | 800 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId(app_id); |
765 if (shelf_app_id.empty()) | |
766 return ash::kInvalidShelfID; | |
767 | 801 |
768 for (const ash::ShelfItem& item : model_->items()) { | 802 for (const ash::ShelfItem& item : model_->items()) { |
769 // Ash's ShelfWindowWatcher handles app panel windows separately. | 803 // Ash's ShelfWindowWatcher handles app panel windows separately. |
770 if (item.type != ash::TYPE_APP_PANEL && | 804 if (item.type != ash::TYPE_APP_PANEL && |
771 item.app_launch_id.app_id() == shelf_app_id && | 805 item.app_launch_id.app_id() == shelf_app_id && |
772 item.app_launch_id.launch_id() == launch_id) { | 806 item.app_launch_id.launch_id() == launch_id) { |
773 return item.id; | 807 return item.id; |
774 } | 808 } |
775 } | 809 } |
776 return ash::kInvalidShelfID; | 810 return ash::kInvalidShelfID; |
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
930 if (item && item->type == ash::TYPE_APP) { | 964 if (item && item->type == ash::TYPE_APP) { |
931 int app_index = model_->ItemIndexByID(item->id); | 965 int app_index = model_->ItemIndexByID(item->id); |
932 DCHECK_GE(app_index, 0); | 966 DCHECK_GE(app_index, 0); |
933 if (running_index != app_index) | 967 if (running_index != app_index) |
934 model_->Move(running_index, app_index); | 968 model_->Move(running_index, app_index); |
935 running_index++; | 969 running_index++; |
936 } | 970 } |
937 } | 971 } |
938 } | 972 } |
939 | 973 |
940 void ChromeLauncherControllerImpl::RemoveShelfItem(ash::ShelfID id) { | 974 void ChromeLauncherControllerImpl::LauncherItemClosed(ash::ShelfID id) { |
941 const std::string& app_id = GetAppIDForShelfID(id); | 975 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); |
| 976 CHECK(iter != id_to_item_controller_map_.end()); |
| 977 CHECK(iter->second); |
| 978 const std::string& app_id = iter->second->app_id(); |
942 AppIconLoader* app_icon_loader = GetAppIconLoaderForApp(app_id); | 979 AppIconLoader* app_icon_loader = GetAppIconLoaderForApp(app_id); |
943 if (app_icon_loader) | 980 if (app_icon_loader) |
944 app_icon_loader->ClearImage(app_id); | 981 app_icon_loader->ClearImage(app_id); |
945 const int index = model_->ItemIndexByID(id); | 982 id_to_item_controller_map_.erase(iter); |
| 983 int index = model_->ItemIndexByID(id); |
946 // A "browser proxy" is not known to the model and this removal does | 984 // A "browser proxy" is not known to the model and this removal does |
947 // therefore not need to be propagated to the model. | 985 // therefore not need to be propagated to the model. |
948 if (index != -1) | 986 if (index != -1) |
949 model_->RemoveItemAt(index); | 987 model_->RemoveItemAt(index); |
950 } | 988 } |
951 | 989 |
952 void ChromeLauncherControllerImpl::PinRunningAppInternal( | 990 void ChromeLauncherControllerImpl::PinRunningAppInternal( |
953 int index, | 991 int index, |
954 ash::ShelfID shelf_id) { | 992 ash::ShelfID shelf_id) { |
955 DCHECK_EQ(GetItem(shelf_id)->type, ash::TYPE_APP); | 993 DCHECK_EQ(GetItem(shelf_id)->type, ash::TYPE_APP); |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1055 if (shelf_app_id != app_id) | 1093 if (shelf_app_id != app_id) |
1056 continue; | 1094 continue; |
1057 | 1095 |
1058 // Update apps icon if applicable. | 1096 // Update apps icon if applicable. |
1059 OnAppUpdated(profile(), app_id); | 1097 OnAppUpdated(profile(), app_id); |
1060 | 1098 |
1061 // Find existing pin or app from the right of current |index|. | 1099 // Find existing pin or app from the right of current |index|. |
1062 int app_index = index; | 1100 int app_index = index; |
1063 for (; app_index < model_->item_count(); ++app_index) { | 1101 for (; app_index < model_->item_count(); ++app_index) { |
1064 const ash::ShelfItem& item = model_->items()[app_index]; | 1102 const ash::ShelfItem& item = model_->items()[app_index]; |
1065 if (item.app_launch_id.app_id() == app_id && | 1103 const IDToItemControllerMap::iterator it = |
1066 item.app_launch_id.launch_id() == pref_app_launch_id.launch_id()) { | 1104 id_to_item_controller_map_.find(item.id); |
| 1105 if (it != id_to_item_controller_map_.end() && |
| 1106 it->second->app_id() == app_id && |
| 1107 it->second->launch_id() == pref_app_launch_id.launch_id()) { |
1067 break; | 1108 break; |
1068 } | 1109 } |
1069 } | 1110 } |
1070 if (app_index < model_->item_count()) { | 1111 if (app_index < model_->item_count()) { |
1071 // Found existing pin or running app. | 1112 // Found existing pin or running app. |
1072 const ash::ShelfItem item = model_->items()[app_index]; | 1113 const ash::ShelfItem item = model_->items()[app_index]; |
1073 if (ItemTypeIsPinned(item)) { | 1114 if (item.type == ash::TYPE_PINNED_APP || |
| 1115 item.type == ash::TYPE_BROWSER_SHORTCUT) { |
1074 // Just move to required position or keep it inplace. | 1116 // Just move to required position or keep it inplace. |
1075 model_->Move(app_index, index); | 1117 model_->Move(app_index, index); |
1076 } else { | 1118 } else { |
1077 PinRunningAppInternal(index, item.id); | 1119 PinRunningAppInternal(index, item.id); |
1078 } | 1120 } |
1079 DCHECK_EQ(model_->ItemIndexByID(item.id), index); | 1121 DCHECK_EQ(model_->ItemIndexByID(item.id), index); |
1080 } else { | 1122 } else { |
1081 // This is fresh pin. Create new one. | 1123 // This is fresh pin. Create new one. |
1082 DCHECK_NE(app_id, kChromeAppId); | 1124 DCHECK_NE(app_id, extension_misc::kChromeAppId); |
1083 CreateAppShortcutLauncherItem(pref_app_launch_id, index); | 1125 CreateAppShortcutLauncherItem(pref_app_launch_id, index); |
1084 } | 1126 } |
1085 ++index; | 1127 ++index; |
1086 } | 1128 } |
1087 | 1129 |
1088 // At second step remove any pin to the right from the current index. | 1130 // At second step remove any pin to the right from the current index. |
1089 while (index < model_->item_count()) { | 1131 while (index < model_->item_count()) { |
1090 const ash::ShelfItem item = model_->items()[index]; | 1132 const ash::ShelfItem item = model_->items()[index]; |
1091 if (item.type == ash::TYPE_PINNED_APP) | 1133 if (item.type == ash::TYPE_PINNED_APP) |
1092 UnpinShelfItemInternal(item.id); | 1134 UnpinShelfItemInternal(item.id); |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1156 ash::ShelfID ChromeLauncherControllerImpl::InsertAppLauncherItem( | 1198 ash::ShelfID ChromeLauncherControllerImpl::InsertAppLauncherItem( |
1157 std::unique_ptr<ash::ShelfItemDelegate> item_delegate, | 1199 std::unique_ptr<ash::ShelfItemDelegate> item_delegate, |
1158 ash::ShelfItemStatus status, | 1200 ash::ShelfItemStatus status, |
1159 int index, | 1201 int index, |
1160 ash::ShelfItemType shelf_item_type) { | 1202 ash::ShelfItemType shelf_item_type) { |
1161 ash::ShelfID id = model_->next_id(); | 1203 ash::ShelfID id = model_->next_id(); |
1162 CHECK(!GetItem(id)); | 1204 CHECK(!GetItem(id)); |
1163 CHECK(item_delegate); | 1205 CHECK(item_delegate); |
1164 // Ash's ShelfWindowWatcher handles app panel windows separately. | 1206 // Ash's ShelfWindowWatcher handles app panel windows separately. |
1165 DCHECK_NE(ash::TYPE_APP_PANEL, shelf_item_type); | 1207 DCHECK_NE(ash::TYPE_APP_PANEL, shelf_item_type); |
| 1208 id_to_item_controller_map_[id] = item_delegate.get(); |
| 1209 item_delegate->set_shelf_id(id); |
1166 | 1210 |
1167 ash::ShelfItem item; | 1211 ash::ShelfItem item; |
1168 item.type = shelf_item_type; | 1212 item.type = shelf_item_type; |
1169 item.app_launch_id = item_delegate->app_launch_id(); | 1213 item.app_launch_id = item_delegate->app_launch_id(); |
1170 item.image = extensions::util::GetDefaultAppIcon(); | 1214 item.image = extensions::util::GetDefaultAppIcon(); |
1171 | 1215 |
1172 const std::string& app_id = item_delegate->app_id(); | 1216 const std::string& app_id = item_delegate->app_id(); |
1173 item.title = LauncherControllerHelper::GetAppTitle(profile(), app_id); | 1217 item.title = LauncherControllerHelper::GetAppTitle(profile(), app_id); |
1174 | 1218 |
1175 ash::ShelfItemStatus new_state = GetAppState(app_id); | 1219 ash::ShelfItemStatus new_state = GetAppState(app_id); |
(...skipping 17 matching lines...) Expand all Loading... |
1193 // Do not sync the pin position of the browser shortcut item when it is added; | 1237 // Do not sync the pin position of the browser shortcut item when it is added; |
1194 // its initial position before prefs have loaded is unimportant and the sync | 1238 // its initial position before prefs have loaded is unimportant and the sync |
1195 // service may not yet be initialized. | 1239 // service may not yet be initialized. |
1196 ScopedPinSyncDisabler scoped_pin_sync_disabler = GetScopedPinSyncDisabler(); | 1240 ScopedPinSyncDisabler scoped_pin_sync_disabler = GetScopedPinSyncDisabler(); |
1197 | 1241 |
1198 ash::ShelfItem browser_shortcut; | 1242 ash::ShelfItem browser_shortcut; |
1199 browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; | 1243 browser_shortcut.type = ash::TYPE_BROWSER_SHORTCUT; |
1200 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | 1244 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
1201 browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); | 1245 browser_shortcut.image = *rb.GetImageSkiaNamed(IDR_PRODUCT_LOGO_32); |
1202 browser_shortcut.title = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); | 1246 browser_shortcut.title = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); |
1203 browser_shortcut.app_launch_id = ash::AppLaunchId(kChromeAppId); | 1247 browser_shortcut.app_launch_id = |
| 1248 ash::AppLaunchId(extension_misc::kChromeAppId); |
1204 ash::ShelfID id = model_->next_id(); | 1249 ash::ShelfID id = model_->next_id(); |
1205 model_->AddAt(0, browser_shortcut); | 1250 model_->AddAt(0, browser_shortcut); |
1206 std::unique_ptr<BrowserShortcutLauncherItemController> item_delegate = | 1251 std::unique_ptr<BrowserShortcutLauncherItemController> item_delegate = |
1207 base::MakeUnique<BrowserShortcutLauncherItemController>(model_); | 1252 base::MakeUnique<BrowserShortcutLauncherItemController>(model_); |
1208 BrowserShortcutLauncherItemController* item_controller = item_delegate.get(); | 1253 BrowserShortcutLauncherItemController* item_controller = item_delegate.get(); |
| 1254 item_controller->set_shelf_id(id); |
| 1255 id_to_item_controller_map_[id] = item_controller; |
1209 model_->SetShelfItemDelegate(id, std::move(item_delegate)); | 1256 model_->SetShelfItemDelegate(id, std::move(item_delegate)); |
1210 item_controller->UpdateBrowserItemState(); | 1257 item_controller->UpdateBrowserItemState(); |
1211 } | 1258 } |
1212 | 1259 |
1213 bool ChromeLauncherControllerImpl::IsIncognito( | 1260 bool ChromeLauncherControllerImpl::IsIncognito( |
1214 const content::WebContents* web_contents) const { | 1261 const content::WebContents* web_contents) const { |
1215 const Profile* profile = | 1262 const Profile* profile = |
1216 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | 1263 Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
1217 return profile->IsOffTheRecord() && !profile->IsGuestSession() && | 1264 return profile->IsOffTheRecord() && !profile->IsGuestSession() && |
1218 !profile->IsSystemProfile(); | 1265 !profile->IsSystemProfile(); |
1219 } | 1266 } |
1220 | 1267 |
1221 int ChromeLauncherControllerImpl::FindInsertionPoint() { | 1268 int ChromeLauncherControllerImpl::FindInsertionPoint() { |
| 1269 DCHECK_GT(model_->item_count(), 0); |
1222 for (int i = model_->item_count() - 1; i > 0; --i) { | 1270 for (int i = model_->item_count() - 1; i > 0; --i) { |
1223 if (ItemTypeIsPinned(model_->items()[i])) | 1271 ash::ShelfItemType type = model_->items()[i].type; |
| 1272 DCHECK_NE(ash::TYPE_APP_LIST, type); |
| 1273 if (type == ash::TYPE_PINNED_APP || type == ash::TYPE_BROWSER_SHORTCUT) |
1224 return i; | 1274 return i; |
1225 } | 1275 } |
1226 return 0; | 1276 return 0; |
1227 } | 1277 } |
1228 | 1278 |
1229 void ChromeLauncherControllerImpl::CloseWindowedAppsFromRemovedExtension( | 1279 void ChromeLauncherControllerImpl::CloseWindowedAppsFromRemovedExtension( |
1230 const std::string& app_id, | 1280 const std::string& app_id, |
1231 const Profile* profile) { | 1281 const Profile* profile) { |
1232 // This function cannot rely on the controller's enumeration functionality | 1282 // This function cannot rely on the controller's enumeration functionality |
1233 // since the extension has already been unloaded. | 1283 // since the extension has already been unloaded. |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1287 // Remove the pin position from preferences as needed. | 1337 // Remove the pin position from preferences as needed. |
1288 if (ItemTypeIsPinned(old_item) && should_sync_pin_changes()) { | 1338 if (ItemTypeIsPinned(old_item) && should_sync_pin_changes()) { |
1289 // TODO(khmel): Fix this Arc application id mapping. See http://b/31703859 | 1339 // TODO(khmel): Fix this Arc application id mapping. See http://b/31703859 |
1290 const std::string shelf_app_id = | 1340 const std::string shelf_app_id = |
1291 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId( | 1341 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId( |
1292 old_item.app_launch_id.app_id()); | 1342 old_item.app_launch_id.app_id()); |
1293 ash::AppLaunchId app_launch_id(shelf_app_id, | 1343 ash::AppLaunchId app_launch_id(shelf_app_id, |
1294 old_item.app_launch_id.launch_id()); | 1344 old_item.app_launch_id.launch_id()); |
1295 ash::launcher::RemovePinPosition(profile(), app_launch_id); | 1345 ash::launcher::RemovePinPosition(profile(), app_launch_id); |
1296 } | 1346 } |
| 1347 |
| 1348 // TODO(skuhne): This fixes crbug.com/429870, but it does not answer why we |
| 1349 // get into this state in the first place. |
| 1350 if (id_to_item_controller_map_.count(old_item.id) > 0) |
| 1351 id_to_item_controller_map_.erase(old_item.id); |
1297 } | 1352 } |
1298 | 1353 |
1299 void ChromeLauncherControllerImpl::ShelfItemMoved(int start_index, | 1354 void ChromeLauncherControllerImpl::ShelfItemMoved(int start_index, |
1300 int target_index) { | 1355 int target_index) { |
1301 // Update the pin position preference as needed. | 1356 // Update the pin position preference as needed. |
1302 const ash::ShelfItem& item = model_->items()[target_index]; | 1357 const ash::ShelfItem& item = model_->items()[target_index]; |
1303 DCHECK_NE(ash::TYPE_APP_LIST, item.type); | 1358 DCHECK_NE(ash::TYPE_APP_LIST, item.type); |
1304 if (ItemTypeIsPinned(item) && should_sync_pin_changes()) | 1359 if (ItemTypeIsPinned(item) && should_sync_pin_changes()) |
1305 SyncPinPosition(item.id); | 1360 SyncPinPosition(item.id); |
1306 } | 1361 } |
(...skipping 13 matching lines...) Expand all Loading... |
1320 const std::string shelf_app_id = | 1375 const std::string shelf_app_id = |
1321 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId( | 1376 ArcAppWindowLauncherController::GetShelfAppIdFromArcAppId( |
1322 old_item.app_launch_id.app_id()); | 1377 old_item.app_launch_id.app_id()); |
1323 | 1378 |
1324 ash::AppLaunchId app_launch_id(shelf_app_id, | 1379 ash::AppLaunchId app_launch_id(shelf_app_id, |
1325 old_item.app_launch_id.launch_id()); | 1380 old_item.app_launch_id.launch_id()); |
1326 ash::launcher::RemovePinPosition(profile(), app_launch_id); | 1381 ash::launcher::RemovePinPosition(profile(), app_launch_id); |
1327 } | 1382 } |
1328 } | 1383 } |
1329 | 1384 |
| 1385 void ChromeLauncherControllerImpl::OnSetShelfItemDelegate( |
| 1386 ash::ShelfID id, |
| 1387 ash::ShelfItemDelegate* item_delegate) { |
| 1388 // TODO(skuhne): This fixes crbug.com/429870, but it does not answer why we |
| 1389 // get into this state in the first place. |
| 1390 IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id); |
| 1391 if (iter == id_to_item_controller_map_.end() || item_delegate == iter->second) |
| 1392 return; |
| 1393 LOG(ERROR) << "Unexpected change of shelf item delegate, id: " << id; |
| 1394 id_to_item_controller_map_.erase(iter); |
| 1395 } |
| 1396 |
1330 /////////////////////////////////////////////////////////////////////////////// | 1397 /////////////////////////////////////////////////////////////////////////////// |
1331 // ash::WindowTreeHostManager::Observer: | 1398 // ash::WindowTreeHostManager::Observer: |
1332 | 1399 |
1333 void ChromeLauncherControllerImpl::OnDisplayConfigurationChanged() { | 1400 void ChromeLauncherControllerImpl::OnDisplayConfigurationChanged() { |
1334 // In BOTTOM_LOCKED state, ignore the call of SetShelfBehaviorsFromPrefs. | 1401 // In BOTTOM_LOCKED state, ignore the call of SetShelfBehaviorsFromPrefs. |
1335 // Because it might be called by some operations, like crbug.com/627040 | 1402 // Because it might be called by some operations, like crbug.com/627040 |
1336 // rotating screen. | 1403 // rotating screen. |
1337 ash::WmShelf* shelf = | 1404 ash::WmShelf* shelf = |
1338 ash::WmShelf::ForWindow(ash::WmShell::Get()->GetPrimaryRootWindow()); | 1405 ash::WmShelf::ForWindow(ash::WmShell::Get()->GetPrimaryRootWindow()); |
1339 if (shelf->alignment() != ash::SHELF_ALIGNMENT_BOTTOM_LOCKED) | 1406 if (shelf->alignment() != ash::SHELF_ALIGNMENT_BOTTOM_LOCKED) |
(...skipping 16 matching lines...) Expand all Loading... |
1356 if (item.title != title) { | 1423 if (item.title != title) { |
1357 item.title = title; | 1424 item.title = title; |
1358 model_->Set(app_list_index, item); | 1425 model_->Set(app_list_index, item); |
1359 } | 1426 } |
1360 } | 1427 } |
1361 | 1428 |
1362 /////////////////////////////////////////////////////////////////////////////// | 1429 /////////////////////////////////////////////////////////////////////////////// |
1363 // AppIconLoaderDelegate: | 1430 // AppIconLoaderDelegate: |
1364 | 1431 |
1365 void ChromeLauncherControllerImpl::OnAppImageUpdated( | 1432 void ChromeLauncherControllerImpl::OnAppImageUpdated( |
1366 const std::string& app_id, | 1433 const std::string& id, |
1367 const gfx::ImageSkia& image) { | 1434 const gfx::ImageSkia& image) { |
1368 // TODO: need to get this working for shortcuts. | 1435 // TODO: need to get this working for shortcuts. |
1369 for (int index = 0; index < model_->item_count(); ++index) { | 1436 for (int index = 0; index < model_->item_count(); ++index) { |
1370 ash::ShelfItem item = model_->items()[index]; | 1437 ash::ShelfItem item = model_->items()[index]; |
1371 ash::ShelfItemDelegate* delegate = model_->GetShelfItemDelegate(item.id); | 1438 if (GetAppIDForShelfID(item.id) != id) |
1372 if (item.type == ash::TYPE_APP_PANEL || !delegate || | |
1373 delegate->image_set_by_controller() || | |
1374 item.app_launch_id.app_id() != app_id) { | |
1375 continue; | 1439 continue; |
1376 } | 1440 ash::ShelfItemDelegate* delegate = GetShelfItemDelegate(item.id); |
| 1441 if (!delegate || delegate->image_set_by_controller()) |
| 1442 continue; |
1377 item.image = image; | 1443 item.image = image; |
1378 if (arc_deferred_launcher_) | 1444 if (arc_deferred_launcher_) |
1379 arc_deferred_launcher_->MaybeApplySpinningEffect(app_id, &item.image); | 1445 arc_deferred_launcher_->MaybeApplySpinningEffect(id, &item.image); |
1380 model_->Set(index, item); | 1446 model_->Set(index, item); |
1381 // It's possible we're waiting on more than one item, so don't break. | 1447 // It's possible we're waiting on more than one item, so don't break. |
1382 } | 1448 } |
1383 } | 1449 } |
OLD | NEW |