Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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/profiles/profile_shortcut_manager_win.h" | 5 #include "chrome/browser/profiles/profile_shortcut_manager_win.h" |
| 6 | 6 |
| 7 #include <shlobj.h> // For SHChangeNotify(). | 7 #include <shlobj.h> // For SHChangeNotify(). |
| 8 | 8 |
| 9 #include <string> | 9 #include <string> |
| 10 #include <vector> | 10 #include <vector> |
| 11 | 11 |
| 12 #include "base/bind.h" | 12 #include "base/bind.h" |
| 13 #include "base/command_line.h" | 13 #include "base/command_line.h" |
| 14 #include "base/files/file_enumerator.h" | 14 #include "base/files/file_enumerator.h" |
| 15 #include "base/files/file_util.h" | 15 #include "base/files/file_util.h" |
| 16 #include "base/path_service.h" | 16 #include "base/path_service.h" |
| 17 #include "base/prefs/pref_service.h" | 17 #include "base/prefs/pref_service.h" |
| 18 #include "base/strings/string16.h" | 18 #include "base/strings/string16.h" |
| 19 #include "base/strings/string_util.h" | 19 #include "base/strings/string_util.h" |
| 20 #include "base/strings/stringprintf.h" | 20 #include "base/strings/stringprintf.h" |
| 21 #include "base/strings/utf_string_conversions.h" | 21 #include "base/strings/utf_string_conversions.h" |
| 22 #include "base/win/shortcut.h" | 22 #include "base/win/shortcut.h" |
| 23 #include "chrome/browser/app_icon_win.h" | 23 #include "chrome/browser/app_icon_win.h" |
| 24 #include "chrome/browser/browser_process.h" | 24 #include "chrome/browser/browser_process.h" |
| 25 #include "chrome/browser/chrome_notification_types.h" | 25 #include "chrome/browser/chrome_notification_types.h" |
| 26 #include "chrome/browser/profiles/profile_attributes_entry.h" | |
| 27 #include "chrome/browser/profiles/profile_attributes_storage.h" | |
| 26 #include "chrome/browser/profiles/profile_avatar_icon_util.h" | 28 #include "chrome/browser/profiles/profile_avatar_icon_util.h" |
| 27 #include "chrome/browser/profiles/profile_info_cache_observer.h" | 29 #include "chrome/browser/profiles/profile_info_cache_observer.h" |
| 28 #include "chrome/browser/profiles/profile_manager.h" | 30 #include "chrome/browser/profiles/profile_manager.h" |
| 29 #include "chrome/browser/shell_integration.h" | 31 #include "chrome/browser/shell_integration.h" |
| 30 #include "chrome/common/chrome_switches.h" | 32 #include "chrome/common/chrome_switches.h" |
| 31 #include "chrome/common/pref_names.h" | 33 #include "chrome/common/pref_names.h" |
| 32 #include "chrome/grit/chromium_strings.h" | 34 #include "chrome/grit/chromium_strings.h" |
| 33 #include "chrome/installer/util/browser_distribution.h" | 35 #include "chrome/installer/util/browser_distribution.h" |
| 34 #include "chrome/installer/util/product.h" | 36 #include "chrome/installer/util/product.h" |
| 35 #include "chrome/installer/util/shell_util.h" | 37 #include "chrome/installer/util/shell_util.h" |
| (...skipping 621 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 657 | 659 |
| 658 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager) | 660 ProfileShortcutManagerWin::ProfileShortcutManagerWin(ProfileManager* manager) |
| 659 : profile_manager_(manager) { | 661 : profile_manager_(manager) { |
| 660 DCHECK_EQ( | 662 DCHECK_EQ( |
| 661 arraysize(kProfileAvatarIconResources2x), | 663 arraysize(kProfileAvatarIconResources2x), |
| 662 profiles::GetDefaultAvatarIconCount()); | 664 profiles::GetDefaultAvatarIconCount()); |
| 663 | 665 |
| 664 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, | 666 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED, |
| 665 content::NotificationService::AllSources()); | 667 content::NotificationService::AllSources()); |
| 666 | 668 |
| 667 profile_manager_->GetProfileInfoCache().AddObserver(this); | 669 profile_manager_->GetProfileAttributesStorage().AddObserver(this); |
| 668 } | 670 } |
| 669 | 671 |
| 670 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { | 672 ProfileShortcutManagerWin::~ProfileShortcutManagerWin() { |
| 671 profile_manager_->GetProfileInfoCache().RemoveObserver(this); | 673 profile_manager_->GetProfileAttributesStorage().RemoveObserver(this); |
| 672 } | 674 } |
| 673 | 675 |
| 674 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon( | 676 void ProfileShortcutManagerWin::CreateOrUpdateProfileIcon( |
| 675 const base::FilePath& profile_path) { | 677 const base::FilePath& profile_path) { |
| 676 CreateOrUpdateShortcutsForProfileAtPath(profile_path, | 678 CreateOrUpdateShortcutsForProfileAtPath(profile_path, |
| 677 CREATE_OR_UPDATE_ICON_ONLY, | 679 CREATE_OR_UPDATE_ICON_ONLY, |
| 678 IGNORE_NON_PROFILE_SHORTCUTS); | 680 IGNORE_NON_PROFILE_SHORTCUTS); |
| 679 } | 681 } |
| 680 | 682 |
| 681 void ProfileShortcutManagerWin::CreateProfileShortcut( | 683 void ProfileShortcutManagerWin::CreateProfileShortcut( |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 703 const base::FilePath& profile_path, | 705 const base::FilePath& profile_path, |
| 704 base::CommandLine* command_line, | 706 base::CommandLine* command_line, |
| 705 base::string16* name, | 707 base::string16* name, |
| 706 base::FilePath* icon_path) { | 708 base::FilePath* icon_path) { |
| 707 base::FilePath chrome_exe; | 709 base::FilePath chrome_exe; |
| 708 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { | 710 if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
| 709 NOTREACHED(); | 711 NOTREACHED(); |
| 710 return; | 712 return; |
| 711 } | 713 } |
| 712 | 714 |
| 713 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); | 715 ProfileAttributesStorage& storage = |
| 714 size_t profile_index = cache.GetIndexOfProfileWithPath(profile_path); | 716 profile_manager_->GetProfileAttributesStorage(); |
| 715 DCHECK_LT(profile_index, cache.GetNumberOfProfiles()); | 717 ProfileAttributesEntry* entry; |
| 718 if (!storage.GetProfileAttributesWithPath(profile_path, &entry)) { | |
|
Mike Lerman
2015/08/06 16:06:20
Isn't this equivalent to DCHECK(storage.GetProfile
| |
| 719 NOTREACHED(); | |
| 720 return; | |
| 721 } | |
| 716 | 722 |
| 717 // The used profile name should be empty if there is only 1 profile. | 723 // The used profile name should be empty if there is only 1 profile. |
| 718 base::string16 shortcut_profile_name; | 724 base::string16 shortcut_profile_name; |
| 719 if (cache.GetNumberOfProfiles() > 1) | 725 if (storage.GetNumberOfProfiles() > 1) |
| 720 shortcut_profile_name = cache.GetNameOfProfileAtIndex(profile_index); | 726 shortcut_profile_name = entry->GetName(); |
| 721 | 727 |
| 722 *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile( | 728 *name = base::FilePath(profiles::internal::GetShortcutFilenameForProfile( |
| 723 shortcut_profile_name, | 729 shortcut_profile_name, |
| 724 BrowserDistribution::GetDistribution())).RemoveExtension().value(); | 730 BrowserDistribution::GetDistribution())).RemoveExtension().value(); |
| 725 | 731 |
| 726 command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " + | 732 command_line->ParseFromString(L"\"" + chrome_exe.value() + L"\" " + |
| 727 profiles::internal::CreateProfileShortcutFlags(profile_path)); | 733 profiles::internal::CreateProfileShortcutFlags(profile_path)); |
| 728 | 734 |
| 729 *icon_path = profiles::internal::GetProfileIconPath(profile_path); | 735 *icon_path = profiles::internal::GetProfileIconPath(profile_path); |
| 730 } | 736 } |
| 731 | 737 |
| 732 void ProfileShortcutManagerWin::OnProfileAdded( | 738 void ProfileShortcutManagerWin::OnProfileAdded( |
| 733 const base::FilePath& profile_path) { | 739 const base::FilePath& profile_path) { |
| 734 CreateOrUpdateProfileIcon(profile_path); | 740 CreateOrUpdateProfileIcon(profile_path); |
| 735 if (profile_manager_->GetProfileInfoCache().GetNumberOfProfiles() == 2) { | 741 if (profile_manager_->GetProfileAttributesStorage(). |
| 742 GetNumberOfProfiles() == 2) { | |
| 736 // When the second profile is added, make existing non-profile shortcuts | 743 // When the second profile is added, make existing non-profile shortcuts |
| 737 // point to the first profile and be badged/named appropriately. | 744 // point to the first profile and be badged/named appropriately. |
| 738 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path), | 745 CreateOrUpdateShortcutsForProfileAtPath(GetOtherProfilePath(profile_path), |
| 739 UPDATE_EXISTING_ONLY, | 746 UPDATE_EXISTING_ONLY, |
| 740 UPDATE_NON_PROFILE_SHORTCUTS); | 747 UPDATE_NON_PROFILE_SHORTCUTS); |
| 741 } | 748 } |
| 742 } | 749 } |
| 743 | 750 |
| 744 void ProfileShortcutManagerWin::OnProfileWasRemoved( | 751 void ProfileShortcutManagerWin::OnProfileWasRemoved( |
| 745 const base::FilePath& profile_path, | 752 const base::FilePath& profile_path, |
| 746 const base::string16& profile_name) { | 753 const base::string16& profile_name) { |
| 747 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); | 754 ProfileAttributesStorage& storage = |
| 755 profile_manager_->GetProfileAttributesStorage(); | |
| 748 // If there is only one profile remaining, remove the badging information | 756 // If there is only one profile remaining, remove the badging information |
| 749 // from an existing shortcut. | 757 // from an existing shortcut. |
| 750 const bool deleting_down_to_last_profile = (cache.GetNumberOfProfiles() == 1); | 758 const bool deleting_down_to_last_profile = storage.GetNumberOfProfiles() == 1; |
| 759 | |
| 751 if (deleting_down_to_last_profile) { | 760 if (deleting_down_to_last_profile) { |
| 761 // The remaining profile should be the last one still in |storage|. | |
| 762 std::vector<ProfileAttributesEntry*> entries = | |
| 763 storage.GetAllProfilesAttributes(); | |
| 764 DCHECK_EQ(1U, entries.size()); | |
|
Mike Lerman
2015/08/06 16:06:20
We're only in this condition if NumberOfProfiles()
| |
| 752 // This is needed to unbadge the icon. | 765 // This is needed to unbadge the icon. |
| 753 CreateOrUpdateShortcutsForProfileAtPath(cache.GetPathOfProfileAtIndex(0), | 766 CreateOrUpdateShortcutsForProfileAtPath(entries[0]->GetPath(), |
| 754 UPDATE_EXISTING_ONLY, | 767 UPDATE_EXISTING_ONLY, |
| 755 IGNORE_NON_PROFILE_SHORTCUTS); | 768 IGNORE_NON_PROFILE_SHORTCUTS); |
| 756 } | 769 } |
| 757 | 770 |
| 758 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | 771 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, |
| 759 base::Bind(&DeleteDesktopShortcuts, | 772 base::Bind(&DeleteDesktopShortcuts, |
| 760 profile_path, | 773 profile_path, |
| 761 deleting_down_to_last_profile)); | 774 deleting_down_to_last_profile)); |
| 762 } | 775 } |
| 763 | 776 |
| 764 void ProfileShortcutManagerWin::OnProfileNameChanged( | 777 void ProfileShortcutManagerWin::OnProfileNameChanged( |
| 765 const base::FilePath& profile_path, | 778 const base::FilePath& profile_path, |
| 766 const base::string16& old_profile_name) { | 779 const base::string16& old_profile_name) { |
| 767 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, | 780 CreateOrUpdateShortcutsForProfileAtPath(profile_path, UPDATE_EXISTING_ONLY, |
| 768 IGNORE_NON_PROFILE_SHORTCUTS); | 781 IGNORE_NON_PROFILE_SHORTCUTS); |
| 769 } | 782 } |
| 770 | 783 |
| 771 void ProfileShortcutManagerWin::OnProfileAvatarChanged( | 784 void ProfileShortcutManagerWin::OnProfileAvatarChanged( |
| 772 const base::FilePath& profile_path) { | 785 const base::FilePath& profile_path) { |
| 773 CreateOrUpdateProfileIcon(profile_path); | 786 CreateOrUpdateProfileIcon(profile_path); |
| 774 } | 787 } |
| 775 | 788 |
| 776 base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath( | 789 base::FilePath ProfileShortcutManagerWin::GetOtherProfilePath( |
| 777 const base::FilePath& profile_path) { | 790 const base::FilePath& profile_path) { |
| 778 const ProfileInfoCache& cache = profile_manager_->GetProfileInfoCache(); | 791 ProfileAttributesStorage& storage = |
| 779 DCHECK_EQ(2U, cache.GetNumberOfProfiles()); | 792 profile_manager_->GetProfileAttributesStorage(); |
| 793 DCHECK_EQ(2U, storage.GetNumberOfProfiles()); | |
| 780 // Get the index of the current profile, in order to find the index of the | 794 // Get the index of the current profile, in order to find the index of the |
| 781 // other profile. | 795 // other profile. |
| 782 size_t current_profile_index = cache.GetIndexOfProfileWithPath(profile_path); | 796 ProfileAttributesEntry* current_entry; |
|
Mike Lerman
2015/08/06 16:06:20
I don't think you need all the extra checks. I thi
| |
| 783 size_t other_profile_index = (current_profile_index == 0) ? 1 : 0; | 797 if (!storage.GetProfileAttributesWithPath(profile_path, ¤t_entry)) { |
| 784 return cache.GetPathOfProfileAtIndex(other_profile_index); | 798 NOTREACHED(); |
| 799 return base::FilePath(); | |
| 800 } | |
| 801 | |
| 802 std::vector<ProfileAttributesEntry*> entries = | |
| 803 storage.GetAllProfilesAttributes(); | |
| 804 DCHECK_EQ(2U, entries.size()); | |
|
Mike Lerman
2015/08/06 16:06:20
you checked number of profiles == 2 at line 793, d
| |
| 805 ProfileAttributesEntry* other_entry = | |
| 806 entries[0]->GetPath() == current_entry->GetPath() ? | |
| 807 entries[1] : entries[0]; | |
| 808 return other_entry->GetPath(); | |
| 785 } | 809 } |
| 786 | 810 |
| 787 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath( | 811 void ProfileShortcutManagerWin::CreateOrUpdateShortcutsForProfileAtPath( |
| 788 const base::FilePath& profile_path, | 812 const base::FilePath& profile_path, |
| 789 CreateOrUpdateMode create_mode, | 813 CreateOrUpdateMode create_mode, |
| 790 NonProfileShortcutAction action) { | 814 NonProfileShortcutAction action) { |
| 791 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || | 815 DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| 792 BrowserThread::CurrentlyOn(BrowserThread::UI)); | 816 BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 793 CreateOrUpdateShortcutsParams params(profile_path, create_mode, action); | 817 CreateOrUpdateShortcutsParams params(profile_path, create_mode, action); |
| 794 | 818 |
| 795 ProfileInfoCache* cache = &profile_manager_->GetProfileInfoCache(); | 819 ProfileAttributesStorage& storage = |
| 796 size_t profile_index = cache->GetIndexOfProfileWithPath(profile_path); | 820 profile_manager_->GetProfileAttributesStorage(); |
| 797 if (profile_index == std::string::npos) | 821 ProfileAttributesEntry* entry; |
| 822 if (!storage.GetProfileAttributesWithPath(profile_path, &entry)) { | |
|
Mike Lerman
2015/08/06 16:06:20
{ curlies }?
| |
| 798 return; | 823 return; |
| 799 bool remove_badging = cache->GetNumberOfProfiles() == 1; | 824 } |
| 825 bool remove_badging = storage.GetNumberOfProfiles() == 1; | |
| 800 | 826 |
| 801 params.old_profile_name = | 827 params.old_profile_name = entry->GetShortcutName(); |
| 802 cache->GetShortcutNameOfProfileAtIndex(profile_index); | |
| 803 | 828 |
| 804 // Exit early if the mode is to update existing profile shortcuts only and | 829 // Exit early if the mode is to update existing profile shortcuts only and |
| 805 // none were ever created for this profile, per the shortcut name not being | 830 // none were ever created for this profile, per the shortcut name not being |
| 806 // set in the profile info cache. | 831 // set in the profile info cache. |
| 807 if (params.old_profile_name.empty() && | 832 if (params.old_profile_name.empty() && |
| 808 create_mode == UPDATE_EXISTING_ONLY && | 833 create_mode == UPDATE_EXISTING_ONLY && |
| 809 action == IGNORE_NON_PROFILE_SHORTCUTS) { | 834 action == IGNORE_NON_PROFILE_SHORTCUTS) { |
| 810 return; | 835 return; |
| 811 } | 836 } |
| 812 | 837 |
| 813 if (!remove_badging) { | 838 if (!remove_badging) { |
| 814 params.profile_name = cache->GetNameOfProfileAtIndex(profile_index); | 839 params.profile_name = entry->GetName(); |
| 815 | 840 |
| 816 // The profile might be using the Gaia avatar, which is not in the | 841 // The profile might be using the Gaia avatar, which is not in the |
| 817 // resources array. | 842 // resources array. |
| 818 bool has_gaia_image = false; | 843 bool has_gaia_image = false; |
| 819 if (cache->IsUsingGAIAPictureOfProfileAtIndex(profile_index)) { | 844 if (entry->IsUsingGAIAPicture()) { |
| 820 const gfx::Image* image = | 845 const gfx::Image* image = entry->GetGAIAPicture(); |
| 821 cache->GetGAIAPictureOfProfileAtIndex(profile_index); | |
| 822 if (image) { | 846 if (image) { |
| 823 params.avatar_image_1x = GetSkBitmapCopy(*image); | 847 params.avatar_image_1x = GetSkBitmapCopy(*image); |
| 824 // Gaia images are 256px, which makes them big enough to use in the | 848 // Gaia images are 256px, which makes them big enough to use in the |
| 825 // large icon case as well. | 849 // large icon case as well. |
| 826 DCHECK_GE(image->Width(), IconUtil::kLargeIconSize); | 850 DCHECK_GE(image->Width(), IconUtil::kLargeIconSize); |
| 827 params.avatar_image_2x = params.avatar_image_1x; | 851 params.avatar_image_2x = params.avatar_image_1x; |
| 828 has_gaia_image = true; | 852 has_gaia_image = true; |
| 829 } | 853 } |
| 830 } | 854 } |
| 831 | 855 |
| 832 // If the profile isn't using a Gaia image, or if the Gaia image did not | 856 // If the profile isn't using a Gaia image, or if the Gaia image did not |
| 833 // exist, revert to the previously used avatar icon. | 857 // exist, revert to the previously used avatar icon. |
| 834 if (!has_gaia_image) { | 858 if (!has_gaia_image) { |
| 835 const size_t icon_index = | 859 const size_t icon_index = entry->GetAvatarIconIndex(); |
| 836 cache->GetAvatarIconIndexOfProfileAtIndex(profile_index); | |
| 837 const int resource_id_1x = | 860 const int resource_id_1x = |
| 838 profiles::GetDefaultAvatarIconResourceIDAtIndex(icon_index); | 861 profiles::GetDefaultAvatarIconResourceIDAtIndex(icon_index); |
| 839 const int resource_id_2x = kProfileAvatarIconResources2x[icon_index]; | 862 const int resource_id_2x = kProfileAvatarIconResources2x[icon_index]; |
| 840 // Make a copy of the SkBitmaps to ensure that we can safely use the image | 863 // Make a copy of the SkBitmaps to ensure that we can safely use the image |
| 841 // data on the FILE thread. | 864 // data on the FILE thread. |
| 842 params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x); | 865 params.avatar_image_1x = GetImageResourceSkBitmapCopy(resource_id_1x); |
| 843 params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x); | 866 params.avatar_image_2x = GetImageResourceSkBitmapCopy(resource_id_2x); |
| 844 } | 867 } |
| 845 } | 868 } |
| 846 BrowserThread::PostTask( | 869 BrowserThread::PostTask( |
| 847 BrowserThread::FILE, FROM_HERE, | 870 BrowserThread::FILE, FROM_HERE, |
| 848 base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params)); | 871 base::Bind(&CreateOrUpdateDesktopShortcutsAndIconForProfile, params)); |
| 849 | 872 |
| 850 cache->SetShortcutNameOfProfileAtIndex(profile_index, | 873 entry->SetShortcutName(params.profile_name); |
| 851 params.profile_name); | |
| 852 } | 874 } |
| 853 | 875 |
| 854 void ProfileShortcutManagerWin::Observe( | 876 void ProfileShortcutManagerWin::Observe( |
| 855 int type, | 877 int type, |
| 856 const content::NotificationSource& source, | 878 const content::NotificationSource& source, |
| 857 const content::NotificationDetails& details) { | 879 const content::NotificationDetails& details) { |
| 858 switch (type) { | 880 switch (type) { |
| 859 // This notification is triggered when a profile is loaded. | 881 // This notification is triggered when a profile is loaded. |
| 860 case chrome::NOTIFICATION_PROFILE_CREATED: { | 882 case chrome::NOTIFICATION_PROFILE_CREATED: { |
| 861 Profile* profile = | 883 Profile* profile = |
| 862 content::Source<Profile>(source).ptr()->GetOriginalProfile(); | 884 content::Source<Profile>(source).ptr()->GetOriginalProfile(); |
| 863 if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) < | 885 if (profile->GetPrefs()->GetInteger(prefs::kProfileIconVersion) < |
| 864 kCurrentProfileIconVersion) { | 886 kCurrentProfileIconVersion) { |
| 865 // Ensure the profile's icon file has been created. | 887 // Ensure the profile's icon file has been created. |
| 866 CreateOrUpdateProfileIcon(profile->GetPath()); | 888 CreateOrUpdateProfileIcon(profile->GetPath()); |
| 867 } | 889 } |
| 868 break; | 890 break; |
| 869 } | 891 } |
| 870 default: | 892 default: |
| 871 NOTREACHED(); | 893 NOTREACHED(); |
| 872 break; | 894 break; |
| 873 } | 895 } |
| 874 } | 896 } |
| OLD | NEW |