root/chrome/browser/profiles/profile_info_cache.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SaveBitmap
  2. ReadBitmap
  3. DeleteBitmap
  4. IsDefaultName
  5. user_data_dir_
  6. AddProfileToCache
  7. AddObserver
  8. RemoveObserver
  9. DeleteProfileFromCache
  10. GetNumberOfProfiles
  11. GetIndexOfProfileWithPath
  12. GetNameOfProfileAtIndex
  13. GetShortcutNameOfProfileAtIndex
  14. GetPathOfProfileAtIndex
  15. GetProfileActiveTimeAtIndex
  16. GetUserNameOfProfileAtIndex
  17. GetAvatarIconOfProfileAtIndex
  18. GetLocalAuthCredentialsOfProfileAtIndex
  19. GetBackgroundStatusOfProfileAtIndex
  20. GetGAIANameOfProfileAtIndex
  21. GetGAIAGivenNameOfProfileAtIndex
  22. GetGAIAPictureOfProfileAtIndex
  23. GetHighResAvatarOfProfileAtIndex
  24. LoadAvatarPictureFromPath
  25. ProfileIsManagedAtIndex
  26. IsOmittedProfileAtIndex
  27. ProfileIsSigninRequiredAtIndex
  28. GetManagedUserIdOfProfileAtIndex
  29. ProfileIsEphemeralAtIndex
  30. ProfileIsUsingDefaultNameAtIndex
  31. OnAvatarPictureLoaded
  32. OnGAIAPictureSaved
  33. IsUsingGAIAPictureOfProfileAtIndex
  34. GetAvatarIconIndexOfProfileAtIndex
  35. SetProfileActiveTimeAtIndex
  36. SetNameOfProfileAtIndex
  37. SetShortcutNameOfProfileAtIndex
  38. SetUserNameOfProfileAtIndex
  39. SetAvatarIconOfProfileAtIndex
  40. SetIsOmittedProfileAtIndex
  41. SetManagedUserIdOfProfileAtIndex
  42. SetLocalAuthCredentialsOfProfileAtIndex
  43. SetBackgroundStatusOfProfileAtIndex
  44. SetGAIANameOfProfileAtIndex
  45. SetGAIAGivenNameOfProfileAtIndex
  46. SetGAIAPictureOfProfileAtIndex
  47. SetIsUsingGAIAPictureOfProfileAtIndex
  48. SetProfileSigninRequiredAtIndex
  49. SetProfileIsEphemeralAtIndex
  50. SetProfileIsUsingDefaultNameAtIndex
  51. ChooseNameForNewProfile
  52. IconIndexIsUnique
  53. ChooseAvatarIconIndexForNewProfile
  54. ChooseAvatarIconIndexForNewProfile
  55. GetUserDataDir
  56. GetDefaultAvatarIconCount
  57. GetDefaultAvatarIconResourceIDAtIndex
  58. GetDefaultAvatarIconUrl
  59. IsDefaultAvatarIconIndex
  60. IsDefaultAvatarIconUrl
  61. GetInfoForProfileAtIndex
  62. SetInfoQuietlyForProfileAtIndex
  63. SetInfoForProfileAtIndex
  64. CacheKeyFromProfilePath
  65. FindPositionForProfile
  66. UpdateSortForProfileIndex
  67. GetProfileNames
  68. RegisterPrefs

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/profiles/profile_info_cache.h"

#include "base/bind.h"
#include "base/file_util.h"
#include "base/format_macros.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/profile_management_switches.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_util.h"

using content::BrowserThread;

namespace {

const char kNameKey[] = "name";
const char kShortcutNameKey[] = "shortcut_name";
const char kGAIANameKey[] = "gaia_name";
const char kGAIAGivenNameKey[] = "gaia_given_name";
const char kUserNameKey[] = "user_name";
const char kIsUsingDefaultName[] = "is_using_default_name";
const char kAvatarIconKey[] = "avatar_icon";
const char kAuthCredentialsKey[] = "local_auth_credentials";
const char kUseGAIAPictureKey[] = "use_gaia_picture";
const char kBackgroundAppsKey[] = "background_apps";
const char kGAIAPictureFileNameKey[] = "gaia_picture_file_name";
const char kIsManagedKey[] = "is_managed";
const char kIsOmittedFromProfileListKey[] = "is_omitted_from_profile_list";
const char kSigninRequiredKey[] = "signin_required";
const char kManagedUserId[] = "managed_user_id";
const char kProfileIsEphemeral[] = "is_ephemeral";
const char kActiveTimeKey[] = "active_time";

const char kDefaultUrlPrefix[] = "chrome://theme/IDR_PROFILE_AVATAR_";
const char kGAIAPictureFileName[] = "Google Profile Picture.png";
const char kHighResAvatarFolderName[] = "Avatars";

const int kDefaultAvatarIconResources[] = {
  IDR_PROFILE_AVATAR_0,
  IDR_PROFILE_AVATAR_1,
  IDR_PROFILE_AVATAR_2,
  IDR_PROFILE_AVATAR_3,
  IDR_PROFILE_AVATAR_4,
  IDR_PROFILE_AVATAR_5,
  IDR_PROFILE_AVATAR_6,
  IDR_PROFILE_AVATAR_7,
  IDR_PROFILE_AVATAR_8,
  IDR_PROFILE_AVATAR_9,
  IDR_PROFILE_AVATAR_10,
  IDR_PROFILE_AVATAR_11,
  IDR_PROFILE_AVATAR_12,
  IDR_PROFILE_AVATAR_13,
  IDR_PROFILE_AVATAR_14,
  IDR_PROFILE_AVATAR_15,
  IDR_PROFILE_AVATAR_16,
  IDR_PROFILE_AVATAR_17,
  IDR_PROFILE_AVATAR_18,
  IDR_PROFILE_AVATAR_19,
  IDR_PROFILE_AVATAR_20,
  IDR_PROFILE_AVATAR_21,
  IDR_PROFILE_AVATAR_22,
  IDR_PROFILE_AVATAR_23,
  IDR_PROFILE_AVATAR_24,
  IDR_PROFILE_AVATAR_25,
};

// File names for the high-res avatar icon resources. In the same order as
// the avatars in kDefaultAvatarIconResources.
const char* kDefaultAvatarIconResourceFileNames[] = {
  "avatar_generic.png",
  "avatar_generic_aqua.png",
  "avatar_generic_blue.png",
  "avatar_generic_green.png",
  "avatar_generic_orange.png",
  "avatar_generic_purple.png",
  "avatar_generic_red.png",
  "avatar_generic_yellow.png",
  "avatar_secret_agent.png",
  "avatar_superhero.png",
  "avatar_volley_ball.png",
  "avatar_businessman.png",
  "avatar_ninja.png",
  "avatar_alien.png",
  "avatar_smiley.png",
  "avatar_flower.png",
  "avatar_pizza.png",
  "avatar_soccer.png",
  "avatar_burger.png",
  "avatar_cat.png",
  "avatar_cupcake.png",
  "avatar_dog.png",
  "avatar_horse.png",
  "avatar_margarita.png",
  "avatar_note.png",
  "avatar_sun_cloud.png",
};

const size_t kDefaultAvatarIconsCount = arraysize(kDefaultAvatarIconResources);

// The first 8 icons are generic.
const size_t kGenericIconCount = 8;

// First eight are generic icons, which use IDS_NUMBERED_PROFILE_NAME.
const int kDefaultNames[] = {
  IDS_DEFAULT_AVATAR_NAME_8,
  IDS_DEFAULT_AVATAR_NAME_9,
  IDS_DEFAULT_AVATAR_NAME_10,
  IDS_DEFAULT_AVATAR_NAME_11,
  IDS_DEFAULT_AVATAR_NAME_12,
  IDS_DEFAULT_AVATAR_NAME_13,
  IDS_DEFAULT_AVATAR_NAME_14,
  IDS_DEFAULT_AVATAR_NAME_15,
  IDS_DEFAULT_AVATAR_NAME_16,
  IDS_DEFAULT_AVATAR_NAME_17,
  IDS_DEFAULT_AVATAR_NAME_18,
  IDS_DEFAULT_AVATAR_NAME_19,
  IDS_DEFAULT_AVATAR_NAME_20,
  IDS_DEFAULT_AVATAR_NAME_21,
  IDS_DEFAULT_AVATAR_NAME_22,
  IDS_DEFAULT_AVATAR_NAME_23,
  IDS_DEFAULT_AVATAR_NAME_24,
  IDS_DEFAULT_AVATAR_NAME_25
};

typedef std::vector<unsigned char> ImageData;

// Writes |data| to disk and takes ownership of the pointer. On completion
// |success| is set to true on success and false on failure.
void SaveBitmap(ImageData* data,
                const base::FilePath& image_path,
                bool* success) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  scoped_ptr<ImageData> data_owner(data);
  *success = false;

  // Make sure the destination directory exists.
  base::FilePath dir = image_path.DirName();
  if (!base::DirectoryExists(dir) && !base::CreateDirectory(dir)) {
    LOG(ERROR) << "Failed to create parent directory.";
    return;
  }

  if (base::WriteFile(image_path, reinterpret_cast<char*>(&(*data)[0]),
                      data->size()) == -1) {
    LOG(ERROR) << "Failed to save image to file.";
    return;
  }

  *success = true;
}

// Reads a PNG from disk and decodes it. If the bitmap was successfully read
// from disk the then |out_image| will contain the bitmap image, otherwise it
// will be NULL.
void ReadBitmap(const base::FilePath& image_path,
                gfx::Image** out_image) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  *out_image = NULL;

  // If the path doesn't exist, don't even try reading it.
  if (!base::PathExists(image_path))
    return;

  std::string image_data;
  if (!base::ReadFileToString(image_path, &image_data)) {
    LOG(ERROR) << "Failed to read PNG file from disk.";
    return;
  }

  gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
      base::RefCountedString::TakeString(&image_data));
  if (image.IsEmpty()) {
    LOG(ERROR) << "Failed to decode PNG file.";
    return;
  }

  *out_image = new gfx::Image(image);
}

void DeleteBitmap(const base::FilePath& image_path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
  base::DeleteFile(image_path, false);
}

bool IsDefaultName(const base::string16& name) {
  // Check if it's a "First user" old-style name.
  if (name == l10n_util::GetStringUTF16(IDS_DEFAULT_PROFILE_NAME))
    return true;

  // Check if it's one of the old-style profile names.
  for (size_t i = 0; i < arraysize(kDefaultNames); ++i) {
    if (name == l10n_util::GetStringUTF16(kDefaultNames[i]))
      return true;
  }

  // Check whether it's one of the "Person %d" style names.
  std::string default_name_format = l10n_util::GetStringFUTF8(
      IDS_NEW_NUMBERED_PROFILE_NAME, base::string16()) + "%d";

  int generic_profile_number;  // Unused. Just a placeholder for sscanf.
  int assignments = sscanf(base::UTF16ToUTF8(name).c_str(),
                           default_name_format.c_str(),
                           &generic_profile_number);
  // Unless it matched the format, this is a custom name.
  return assignments == 1;
}

}  // namespace

ProfileInfoCache::ProfileInfoCache(PrefService* prefs,
                                   const base::FilePath& user_data_dir)
    : prefs_(prefs),
      user_data_dir_(user_data_dir) {
  // Populate the cache
  DictionaryPrefUpdate update(prefs_, prefs::kProfileInfoCache);
  base::DictionaryValue* cache = update.Get();
  for (base::DictionaryValue::Iterator it(*cache);
       !it.IsAtEnd(); it.Advance()) {
    base::DictionaryValue* info = NULL;
    cache->GetDictionaryWithoutPathExpansion(it.key(), &info);
    base::string16 name;
    info->GetString(kNameKey, &name);
    sorted_keys_.insert(FindPositionForProfile(it.key(), name), it.key());
    // TODO(ibraaaa): delete this when 97% of our users are using M31.
    // http://crbug.com/276163
    bool is_managed = false;
    if (info->GetBoolean(kIsManagedKey, &is_managed)) {
      info->Remove(kIsManagedKey, NULL);
      info->SetString(kManagedUserId, is_managed ? "DUMMY_ID" : std::string());
    }
    info->SetBoolean(kIsUsingDefaultName, IsDefaultName(name));
  }
}

ProfileInfoCache::~ProfileInfoCache() {
  STLDeleteContainerPairSecondPointers(
      cached_avatar_images_.begin(), cached_avatar_images_.end());
}

void ProfileInfoCache::AddProfileToCache(const base::FilePath& profile_path,
                                         const base::string16& name,
                                         const base::string16& username,
                                         size_t icon_index,
                                         const std::string& managed_user_id) {
  std::string key = CacheKeyFromProfilePath(profile_path);
  DictionaryPrefUpdate update(prefs_, prefs::kProfileInfoCache);
  base::DictionaryValue* cache = update.Get();

  scoped_ptr<base::DictionaryValue> info(new base::DictionaryValue);
  info->SetString(kNameKey, name);
  info->SetString(kUserNameKey, username);
  info->SetString(kAvatarIconKey, GetDefaultAvatarIconUrl(icon_index));
  // Default value for whether background apps are running is false.
  info->SetBoolean(kBackgroundAppsKey, false);
  info->SetString(kManagedUserId, managed_user_id);
  info->SetBoolean(kIsOmittedFromProfileListKey, !managed_user_id.empty());
  info->SetBoolean(kProfileIsEphemeral, false);
  info->SetBoolean(kIsUsingDefaultName, IsDefaultName(name));
  cache->SetWithoutPathExpansion(key, info.release());

  sorted_keys_.insert(FindPositionForProfile(key, name), key);

  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileAdded(profile_path));

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
      content::NotificationService::AllSources(),
      content::NotificationService::NoDetails());
}

void ProfileInfoCache::AddObserver(ProfileInfoCacheObserver* obs) {
  observer_list_.AddObserver(obs);
}

void ProfileInfoCache::RemoveObserver(ProfileInfoCacheObserver* obs) {
  observer_list_.RemoveObserver(obs);
}

void ProfileInfoCache::DeleteProfileFromCache(
    const base::FilePath& profile_path) {
  size_t profile_index = GetIndexOfProfileWithPath(profile_path);
  if (profile_index == std::string::npos) {
    NOTREACHED();
    return;
  }
  base::string16 name = GetNameOfProfileAtIndex(profile_index);

  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileWillBeRemoved(profile_path));

  DictionaryPrefUpdate update(prefs_, prefs::kProfileInfoCache);
  base::DictionaryValue* cache = update.Get();
  std::string key = CacheKeyFromProfilePath(profile_path);
  cache->Remove(key, NULL);
  sorted_keys_.erase(std::find(sorted_keys_.begin(), sorted_keys_.end(), key));

  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileWasRemoved(profile_path, name));

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
      content::NotificationService::AllSources(),
      content::NotificationService::NoDetails());
}

size_t ProfileInfoCache::GetNumberOfProfiles() const {
  return sorted_keys_.size();
}

size_t ProfileInfoCache::GetIndexOfProfileWithPath(
    const base::FilePath& profile_path) const {
  if (profile_path.DirName() != user_data_dir_)
    return std::string::npos;
  std::string search_key = CacheKeyFromProfilePath(profile_path);
  for (size_t i = 0; i < sorted_keys_.size(); ++i) {
    if (sorted_keys_[i] == search_key)
      return i;
  }
  return std::string::npos;
}

base::string16 ProfileInfoCache::GetNameOfProfileAtIndex(size_t index) const {
  base::string16 name;
  // Unless the user has customized the profile name, we should use the
  // profile's Gaia given name, if it's available.
  if (ProfileIsUsingDefaultNameAtIndex(index)) {
    base::string16 given_name = GetGAIAGivenNameOfProfileAtIndex(index);
    name = given_name.empty() ? GetGAIANameOfProfileAtIndex(index) : given_name;
  }
  if (name.empty())
    GetInfoForProfileAtIndex(index)->GetString(kNameKey, &name);
  return name;
}

base::string16 ProfileInfoCache::GetShortcutNameOfProfileAtIndex(size_t index)
    const {
  base::string16 shortcut_name;
  GetInfoForProfileAtIndex(index)->GetString(
      kShortcutNameKey, &shortcut_name);
  return shortcut_name;
}

base::FilePath ProfileInfoCache::GetPathOfProfileAtIndex(size_t index) const {
  return user_data_dir_.AppendASCII(sorted_keys_[index]);
}

base::Time ProfileInfoCache::GetProfileActiveTimeAtIndex(size_t index) const {
  double dt;
  if (GetInfoForProfileAtIndex(index)->GetDouble(kActiveTimeKey, &dt)) {
    return base::Time::FromDoubleT(dt);
  } else {
    return base::Time();
  }
}

base::string16 ProfileInfoCache::GetUserNameOfProfileAtIndex(
    size_t index) const {
  base::string16 user_name;
  GetInfoForProfileAtIndex(index)->GetString(kUserNameKey, &user_name);
  return user_name;
}

const gfx::Image& ProfileInfoCache::GetAvatarIconOfProfileAtIndex(
    size_t index) const {
  if (IsUsingGAIAPictureOfProfileAtIndex(index)) {
    const gfx::Image* image = GetGAIAPictureOfProfileAtIndex(index);
    if (image)
      return *image;
  }

  // Use the high resolution version of the avatar if it exists.
  if (switches::IsNewProfileManagement()) {
    const gfx::Image* image = GetHighResAvatarOfProfileAtIndex(index);
    if (image)
      return *image;
  }

  int resource_id = GetDefaultAvatarIconResourceIDAtIndex(
      GetAvatarIconIndexOfProfileAtIndex(index));
  return ResourceBundle::GetSharedInstance().GetNativeImageNamed(resource_id);
}

std::string ProfileInfoCache::GetLocalAuthCredentialsOfProfileAtIndex(
    size_t index) const {
  std::string credentials;
  GetInfoForProfileAtIndex(index)->GetString(kAuthCredentialsKey, &credentials);
  return credentials;
}

bool ProfileInfoCache::GetBackgroundStatusOfProfileAtIndex(
    size_t index) const {
  bool background_app_status;
  if (!GetInfoForProfileAtIndex(index)->GetBoolean(kBackgroundAppsKey,
                                                   &background_app_status)) {
    return false;
  }
  return background_app_status;
}

base::string16 ProfileInfoCache::GetGAIANameOfProfileAtIndex(
    size_t index) const {
  base::string16 name;
  GetInfoForProfileAtIndex(index)->GetString(kGAIANameKey, &name);
  return name;
}

base::string16 ProfileInfoCache::GetGAIAGivenNameOfProfileAtIndex(
    size_t index) const {
  base::string16 name;
  GetInfoForProfileAtIndex(index)->GetString(kGAIAGivenNameKey, &name);
  return name;
}

const gfx::Image* ProfileInfoCache::GetGAIAPictureOfProfileAtIndex(
    size_t index) const {
  base::FilePath path = GetPathOfProfileAtIndex(index);
  std::string key = CacheKeyFromProfilePath(path);

  std::string file_name;
  GetInfoForProfileAtIndex(index)->GetString(
      kGAIAPictureFileNameKey, &file_name);

  // If the picture is not on disk then return NULL.
  if (file_name.empty())
    return NULL;

  base::FilePath image_path = path.AppendASCII(file_name);
  return LoadAvatarPictureFromPath(key, image_path);
}

const gfx::Image* ProfileInfoCache::GetHighResAvatarOfProfileAtIndex(
    size_t index) const {
  int avatar_index = GetAvatarIconIndexOfProfileAtIndex(index);
  std::string key = kDefaultAvatarIconResourceFileNames[avatar_index];

  base::FilePath user_data_dir;
  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
  base::FilePath image_path =
      user_data_dir.AppendASCII(kHighResAvatarFolderName).AppendASCII(key);
  return LoadAvatarPictureFromPath(key, image_path);
}

const gfx::Image* ProfileInfoCache::LoadAvatarPictureFromPath(
    const std::string& key,
    const base::FilePath& image_path) const {
  // If the picture is already loaded then use it.
  if (cached_avatar_images_.count(key)) {
    if (cached_avatar_images_[key]->IsEmpty())
      return NULL;
    return cached_avatar_images_[key];
  }

  // If the picture is already being loaded then don't try loading it again.
  if (cached_avatar_images_loading_[key])
    return NULL;
  cached_avatar_images_loading_[key] = true;

  gfx::Image** image = new gfx::Image*;
  BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE,
      base::Bind(&ReadBitmap, image_path, image),
      base::Bind(&ProfileInfoCache::OnAvatarPictureLoaded,
          const_cast<ProfileInfoCache*>(this)->AsWeakPtr(), key, image));
  return NULL;
}

bool ProfileInfoCache::ProfileIsManagedAtIndex(size_t index) const {
  return !GetManagedUserIdOfProfileAtIndex(index).empty();
}

bool ProfileInfoCache::IsOmittedProfileAtIndex(size_t index) const {
  bool value = false;
  GetInfoForProfileAtIndex(index)->GetBoolean(kIsOmittedFromProfileListKey,
                                              &value);
  return value;
}

bool ProfileInfoCache::ProfileIsSigninRequiredAtIndex(size_t index) const {
  bool value = false;
  GetInfoForProfileAtIndex(index)->GetBoolean(kSigninRequiredKey, &value);
  return value;
}

std::string ProfileInfoCache::GetManagedUserIdOfProfileAtIndex(
    size_t index) const {
  std::string managed_user_id;
  GetInfoForProfileAtIndex(index)->GetString(kManagedUserId, &managed_user_id);
  return managed_user_id;
}

bool ProfileInfoCache::ProfileIsEphemeralAtIndex(size_t index) const {
  bool value = false;
  GetInfoForProfileAtIndex(index)->GetBoolean(kProfileIsEphemeral, &value);
  return value;
}

bool ProfileInfoCache::ProfileIsUsingDefaultNameAtIndex(size_t index) const {
  bool value = false;
  GetInfoForProfileAtIndex(index)->GetBoolean(kIsUsingDefaultName, &value);
  return value;
}

void ProfileInfoCache::OnAvatarPictureLoaded(const std::string& key,
                                             gfx::Image** image) const {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  cached_avatar_images_loading_[key] = false;

  delete cached_avatar_images_[key];
  if (*image) {
    cached_avatar_images_[key] = *image;
  } else {
    // Place an empty image in the cache to avoid reloading it again.
    cached_avatar_images_[key] = new gfx::Image();
  }
  delete image;

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
      content::NotificationService::AllSources(),
      content::NotificationService::NoDetails());
}

void ProfileInfoCache::OnGAIAPictureSaved(const base::FilePath& path,
                                          bool* success) const  {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (*success) {
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_PROFILE_CACHE_PICTURE_SAVED,
        content::NotificationService::AllSources(),
        content::NotificationService::NoDetails());
  }
  delete success;
}

bool ProfileInfoCache::IsUsingGAIAPictureOfProfileAtIndex(
    size_t index) const {
  bool value = false;
  GetInfoForProfileAtIndex(index)->GetBoolean(kUseGAIAPictureKey, &value);
  return value;
}

size_t ProfileInfoCache::GetAvatarIconIndexOfProfileAtIndex(size_t index)
    const {
  std::string icon_url;
  GetInfoForProfileAtIndex(index)->GetString(kAvatarIconKey, &icon_url);
  size_t icon_index = 0;
  if (!IsDefaultAvatarIconUrl(icon_url, &icon_index))
    DLOG(WARNING) << "Unknown avatar icon: " << icon_url;

  return icon_index;
}

void ProfileInfoCache::SetProfileActiveTimeAtIndex(size_t index) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetDouble(kActiveTimeKey, base::Time::Now().ToDoubleT());
  // This takes ownership of |info|.
  SetInfoQuietlyForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetNameOfProfileAtIndex(size_t index,
                                               const base::string16& name) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  base::string16 current_name;
  info->GetString(kNameKey, &current_name);
  if (name == current_name)
    return;

  base::string16 old_display_name = GetNameOfProfileAtIndex(index);
  info->SetString(kNameKey, name);
  info->SetBoolean(kIsUsingDefaultName, false);

  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
  base::string16 new_display_name = GetNameOfProfileAtIndex(index);
  base::FilePath profile_path = GetPathOfProfileAtIndex(index);
  UpdateSortForProfileIndex(index);

  if (old_display_name != new_display_name) {
    FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                      observer_list_,
                      OnProfileNameChanged(profile_path, old_display_name));
  }
}

void ProfileInfoCache::SetShortcutNameOfProfileAtIndex(
    size_t index,
    const base::string16& shortcut_name) {
  if (shortcut_name == GetShortcutNameOfProfileAtIndex(index))
    return;
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kShortcutNameKey, shortcut_name);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetUserNameOfProfileAtIndex(
    size_t index,
    const base::string16& user_name) {
  if (user_name == GetUserNameOfProfileAtIndex(index))
    return;

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kUserNameKey, user_name);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetAvatarIconOfProfileAtIndex(size_t index,
                                                     size_t icon_index) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kAvatarIconKey, GetDefaultAvatarIconUrl(icon_index));
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());

  base::FilePath profile_path = GetPathOfProfileAtIndex(index);
  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileAvatarChanged(profile_path));
}

void ProfileInfoCache::SetIsOmittedProfileAtIndex(size_t index,
                                                  bool is_omitted) {
  if (IsOmittedProfileAtIndex(index) == is_omitted)
    return;
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kIsOmittedFromProfileListKey, is_omitted);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetManagedUserIdOfProfileAtIndex(size_t index,
                                                        const std::string& id) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kManagedUserId, id);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetLocalAuthCredentialsOfProfileAtIndex(
    size_t index,
    const std::string& credentials) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kAuthCredentialsKey, credentials);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetBackgroundStatusOfProfileAtIndex(
    size_t index,
    bool running_background_apps) {
  if (GetBackgroundStatusOfProfileAtIndex(index) == running_background_apps)
    return;
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kBackgroundAppsKey, running_background_apps);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetGAIANameOfProfileAtIndex(size_t index,
                                                   const base::string16& name) {
  if (name == GetGAIANameOfProfileAtIndex(index))
    return;

  base::string16 old_display_name = GetNameOfProfileAtIndex(index);
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kGAIANameKey, name);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
  base::string16 new_display_name = GetNameOfProfileAtIndex(index);
  base::FilePath profile_path = GetPathOfProfileAtIndex(index);
  UpdateSortForProfileIndex(index);

  if (old_display_name != new_display_name) {
    FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                      observer_list_,
                      OnProfileNameChanged(profile_path, old_display_name));
  }
}

void ProfileInfoCache::SetGAIAGivenNameOfProfileAtIndex(
    size_t index,
    const base::string16& name) {
  if (name == GetGAIAGivenNameOfProfileAtIndex(index))
    return;

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kGAIAGivenNameKey, name);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetGAIAPictureOfProfileAtIndex(size_t index,
                                                      const gfx::Image* image) {
  base::FilePath path = GetPathOfProfileAtIndex(index);
  std::string key = CacheKeyFromProfilePath(path);

  // Delete the old bitmap from cache.
  std::map<std::string, gfx::Image*>::iterator it =
      cached_avatar_images_.find(key);
  if (it != cached_avatar_images_.end()) {
    delete it->second;
    cached_avatar_images_.erase(it);
  }

  std::string old_file_name;
  GetInfoForProfileAtIndex(index)->GetString(
      kGAIAPictureFileNameKey, &old_file_name);
  std::string new_file_name;

  if (!image) {
    // Delete the old bitmap from disk.
    if (!old_file_name.empty()) {
      base::FilePath image_path = path.AppendASCII(old_file_name);
      BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                              base::Bind(&DeleteBitmap, image_path));
    }
  } else {
    // Save the new bitmap to disk.
    cached_avatar_images_[key] = new gfx::Image(*image);
    scoped_ptr<ImageData> data(new ImageData);
    scoped_refptr<base::RefCountedMemory> png_data = image->As1xPNGBytes();
    data->assign(png_data->front(), png_data->front() + png_data->size());
    if (!data->size()) {
      LOG(ERROR) << "Failed to PNG encode the image.";
    } else {
      new_file_name =
          old_file_name.empty() ? kGAIAPictureFileName : old_file_name;
      base::FilePath image_path = path.AppendASCII(new_file_name);
      bool* success = new bool;
      BrowserThread::PostTaskAndReply(BrowserThread::FILE, FROM_HERE,
          base::Bind(&SaveBitmap, data.release(), image_path, success),
          base::Bind(&ProfileInfoCache::OnGAIAPictureSaved, AsWeakPtr(),
                     path, success));
    }
  }

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetString(kGAIAPictureFileNameKey, new_file_name);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());

  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileAvatarChanged(path));
}

void ProfileInfoCache::SetIsUsingGAIAPictureOfProfileAtIndex(size_t index,
                                                             bool value) {
  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kUseGAIAPictureKey, value);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());

  // Retrieve some info to update observers who care about avatar changes.
  base::FilePath profile_path = GetPathOfProfileAtIndex(index);
  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileAvatarChanged(profile_path));
}

void ProfileInfoCache::SetProfileSigninRequiredAtIndex(size_t index,
                                                       bool value) {
  if (value == ProfileIsSigninRequiredAtIndex(index))
    return;

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kSigninRequiredKey, value);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());

  base::FilePath profile_path = GetPathOfProfileAtIndex(index);
  FOR_EACH_OBSERVER(ProfileInfoCacheObserver,
                    observer_list_,
                    OnProfileSigninRequiredChanged(profile_path));
}

void ProfileInfoCache::SetProfileIsEphemeralAtIndex(size_t index, bool value) {
  if (value == ProfileIsEphemeralAtIndex(index))
    return;

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kProfileIsEphemeral, value);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

void ProfileInfoCache::SetProfileIsUsingDefaultNameAtIndex(
    size_t index, bool value) {
  if (value == ProfileIsUsingDefaultNameAtIndex(index))
    return;

  scoped_ptr<base::DictionaryValue> info(
      GetInfoForProfileAtIndex(index)->DeepCopy());
  info->SetBoolean(kIsUsingDefaultName, value);
  // This takes ownership of |info|.
  SetInfoForProfileAtIndex(index, info.release());
}

base::string16 ProfileInfoCache::ChooseNameForNewProfile(
    size_t icon_index) const {
  base::string16 name;
  for (int name_index = 1; ; ++name_index) {
    if (switches::IsNewProfileManagement()) {
      name = l10n_util::GetStringFUTF16Int(IDS_NEW_NUMBERED_PROFILE_NAME,
                                           name_index);
    } else if (icon_index < kGenericIconCount) {
      name = l10n_util::GetStringFUTF16Int(IDS_NUMBERED_PROFILE_NAME,
                                           name_index);
    } else {
      name = l10n_util::GetStringUTF16(
          kDefaultNames[icon_index - kGenericIconCount]);
      if (name_index > 1)
        name.append(base::UTF8ToUTF16(base::IntToString(name_index)));
    }

    // Loop through previously named profiles to ensure we're not duplicating.
    bool name_found = false;
    for (size_t i = 0; i < GetNumberOfProfiles(); ++i) {
      if (GetNameOfProfileAtIndex(i) == name) {
        name_found = true;
        break;
      }
    }
    if (!name_found)
      return name;
  }
}

bool ProfileInfoCache::IconIndexIsUnique(size_t icon_index) const {
  for (size_t i = 0; i < GetNumberOfProfiles(); ++i) {
    if (GetAvatarIconIndexOfProfileAtIndex(i) == icon_index)
      return false;
  }
  return true;
}

bool ProfileInfoCache::ChooseAvatarIconIndexForNewProfile(
    bool allow_generic_icon,
    bool must_be_unique,
    size_t* out_icon_index) const {
  // Always allow all icons for new profiles if using the
  // --new-profile-management flag.
  if (switches::IsNewProfileManagement())
    allow_generic_icon = true;
  size_t start = allow_generic_icon ? 0 : kGenericIconCount;
  size_t end = GetDefaultAvatarIconCount();
  size_t count = end - start;

  int rand = base::RandInt(0, count);
  for (size_t i = 0; i < count; ++i) {
    size_t icon_index = start + (rand + i) %  count;
    if (!must_be_unique || IconIndexIsUnique(icon_index)) {
      *out_icon_index = icon_index;
      return true;
    }
  }

  return false;
}

size_t ProfileInfoCache::ChooseAvatarIconIndexForNewProfile() const {
  size_t icon_index = 0;
  // Try to find a unique, non-generic icon.
  if (ChooseAvatarIconIndexForNewProfile(false, true, &icon_index))
    return icon_index;
  // Try to find any unique icon.
  if (ChooseAvatarIconIndexForNewProfile(true, true, &icon_index))
    return icon_index;
  // Settle for any random icon, even if it's not unique.
  if (ChooseAvatarIconIndexForNewProfile(true, false, &icon_index))
    return icon_index;

  NOTREACHED();
  return 0;
}

const base::FilePath& ProfileInfoCache::GetUserDataDir() const {
  return user_data_dir_;
}

// static
size_t ProfileInfoCache::GetDefaultAvatarIconCount() {
  return kDefaultAvatarIconsCount;
}

// static
int ProfileInfoCache::GetDefaultAvatarIconResourceIDAtIndex(size_t index) {
  DCHECK(IsDefaultAvatarIconIndex(index));
  return kDefaultAvatarIconResources[index];
}

// static
std::string ProfileInfoCache::GetDefaultAvatarIconUrl(size_t index) {
  DCHECK(IsDefaultAvatarIconIndex(index));
  return base::StringPrintf("%s%" PRIuS, kDefaultUrlPrefix, index);
}

// static
bool ProfileInfoCache::IsDefaultAvatarIconIndex(size_t index) {
  return index < kDefaultAvatarIconsCount;
}

// static
bool ProfileInfoCache::IsDefaultAvatarIconUrl(const std::string& url,
                                              size_t* icon_index) {
  DCHECK(icon_index);
  if (url.find(kDefaultUrlPrefix) != 0)
    return false;

  int int_value = -1;
  if (base::StringToInt(base::StringPiece(url.begin() +
                                          strlen(kDefaultUrlPrefix),
                                          url.end()),
                        &int_value)) {
    if (int_value < 0 ||
        int_value >= static_cast<int>(kDefaultAvatarIconsCount))
      return false;
    *icon_index = int_value;
    return true;
  }

  return false;
}

const base::DictionaryValue* ProfileInfoCache::GetInfoForProfileAtIndex(
    size_t index) const {
  DCHECK_LT(index, GetNumberOfProfiles());
  const base::DictionaryValue* cache =
      prefs_->GetDictionary(prefs::kProfileInfoCache);
  const base::DictionaryValue* info = NULL;
  cache->GetDictionaryWithoutPathExpansion(sorted_keys_[index], &info);
  return info;
}

void ProfileInfoCache::SetInfoQuietlyForProfileAtIndex(
    size_t index, base::DictionaryValue* info) {
  DictionaryPrefUpdate update(prefs_, prefs::kProfileInfoCache);
  base::DictionaryValue* cache = update.Get();
  cache->SetWithoutPathExpansion(sorted_keys_[index], info);
}

// TODO(noms): Switch to newer notification system.
void ProfileInfoCache::SetInfoForProfileAtIndex(size_t index,
                                                base::DictionaryValue* info) {
  SetInfoQuietlyForProfileAtIndex(index, info);

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
      content::NotificationService::AllSources(),
      content::NotificationService::NoDetails());
}

std::string ProfileInfoCache::CacheKeyFromProfilePath(
    const base::FilePath& profile_path) const {
  DCHECK(user_data_dir_ == profile_path.DirName());
  base::FilePath base_name = profile_path.BaseName();
  return base_name.MaybeAsASCII();
}

std::vector<std::string>::iterator ProfileInfoCache::FindPositionForProfile(
    const std::string& search_key,
    const base::string16& search_name) {
  base::string16 search_name_l = base::i18n::ToLower(search_name);
  for (size_t i = 0; i < GetNumberOfProfiles(); ++i) {
    base::string16 name_l = base::i18n::ToLower(GetNameOfProfileAtIndex(i));
    int name_compare = search_name_l.compare(name_l);
    if (name_compare < 0)
      return sorted_keys_.begin() + i;
    if (name_compare == 0) {
      int key_compare = search_key.compare(sorted_keys_[i]);
      if (key_compare < 0)
        return sorted_keys_.begin() + i;
    }
  }
  return sorted_keys_.end();
}

void ProfileInfoCache::UpdateSortForProfileIndex(size_t index) {
  base::string16 name = GetNameOfProfileAtIndex(index);

  // Remove and reinsert key in |sorted_keys_| to alphasort.
  std::string key = CacheKeyFromProfilePath(GetPathOfProfileAtIndex(index));
  std::vector<std::string>::iterator key_it =
      std::find(sorted_keys_.begin(), sorted_keys_.end(), key);
  DCHECK(key_it != sorted_keys_.end());
  sorted_keys_.erase(key_it);
  sorted_keys_.insert(FindPositionForProfile(key, name), key);

  content::NotificationService::current()->Notify(
      chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
      content::NotificationService::AllSources(),
      content::NotificationService::NoDetails());
}

// static
std::vector<base::string16> ProfileInfoCache::GetProfileNames() {
  std::vector<base::string16> names;
  PrefService* local_state = g_browser_process->local_state();
  const base::DictionaryValue* cache = local_state->GetDictionary(
      prefs::kProfileInfoCache);
  base::string16 name;
  for (base::DictionaryValue::Iterator it(*cache); !it.IsAtEnd();
       it.Advance()) {
    const base::DictionaryValue* info = NULL;
    it.value().GetAsDictionary(&info);
    info->GetString(kNameKey, &name);
    names.push_back(name);
  }
  return names;
}

// static
void ProfileInfoCache::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterDictionaryPref(prefs::kProfileInfoCache);
}

/* [<][>][^][v][top][bottom][index][help] */