// 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/extensions/api/management/management_api.h"

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/profiler/scoped_profile.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/api/management/management_api_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/extensions/extension_uninstall_dialog.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/extensions/window_controller.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
#include "chrome/common/extensions/api/management.h"
#include "chrome/common/extensions/chrome_utility_extensions_messages.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/browser/utility_process_host.h"
#include "content/public/browser/utility_process_host_client.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/manifest_handlers/offline_enabled_info.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern.h"

using base::IntToString;
using content::BrowserThread;
using content::UtilityProcessHost;
using content::UtilityProcessHostClient;

namespace keys = extension_management_api_constants;

namespace extensions {

namespace management = api::management;

namespace {

typedef std::vector<linked_ptr<management::ExtensionInfo> > ExtensionInfoList;
typedef std::vector<linked_ptr<management::IconInfo> > IconInfoList;

enum AutoConfirmForTest {
  DO_NOT_SKIP = 0,
  PROCEED,
  ABORT
};

AutoConfirmForTest auto_confirm_for_test = DO_NOT_SKIP;

std::vector<std::string> CreateWarningsList(const Extension* extension) {
  std::vector<std::string> warnings_list;
  PermissionMessages warnings =
      extension->permissions_data()->GetPermissionMessages();
  for (PermissionMessages::const_iterator iter = warnings.begin();
       iter != warnings.end(); ++iter) {
    warnings_list.push_back(base::UTF16ToUTF8(iter->message()));
  }

  return warnings_list;
}

std::vector<management::LaunchType> GetAvailableLaunchTypes(
    const Extension& extension) {
  std::vector<management::LaunchType> launch_type_list;
  if (extension.is_platform_app()) {
    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
    return launch_type_list;
  }

  launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB);

#if !defined(OS_MACOSX)
  launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_WINDOW);
#endif

  if (!util::IsStreamlinedHostedAppsEnabled()) {
    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB);
    launch_type_list.push_back(management::LAUNCH_TYPE_OPEN_FULL_SCREEN);
  }
  return launch_type_list;
}

scoped_ptr<management::ExtensionInfo> CreateExtensionInfo(
    const Extension& extension,
    ExtensionSystem* system) {
  scoped_ptr<management::ExtensionInfo> info(new management::ExtensionInfo());
  ExtensionService* service = system->extension_service();

  info->id = extension.id();
  info->name = extension.name();
  info->short_name = extension.short_name();
  info->enabled = service->IsExtensionEnabled(info->id);
  info->offline_enabled = OfflineEnabledInfo::IsOfflineEnabled(&extension);
  info->version = extension.VersionString();
  info->description = extension.description();
  info->options_url = OptionsPageInfo::GetOptionsPage(&extension).spec();
  info->homepage_url.reset(new std::string(
      ManifestURL::GetHomepageURL(&extension).spec()));
  info->may_disable = system->management_policy()->
      UserMayModifySettings(&extension, NULL);
  info->is_app = extension.is_app();
  if (info->is_app) {
    if (extension.is_legacy_packaged_app())
      info->type = management::ExtensionInfo::TYPE_LEGACY_PACKAGED_APP;
    else if (extension.is_hosted_app())
      info->type = management::ExtensionInfo::TYPE_HOSTED_APP;
    else
      info->type = management::ExtensionInfo::TYPE_PACKAGED_APP;
  } else if (extension.is_theme()) {
    info->type = management::ExtensionInfo::TYPE_THEME;
  } else {
    info->type = management::ExtensionInfo::TYPE_EXTENSION;
  }

  if (info->enabled) {
    info->disabled_reason = management::ExtensionInfo::DISABLED_REASON_NONE;
  } else {
    ExtensionPrefs* prefs = ExtensionPrefs::Get(service->profile());
    if (prefs->DidExtensionEscalatePermissions(extension.id())) {
      info->disabled_reason =
          management::ExtensionInfo::DISABLED_REASON_PERMISSIONS_INCREASE;
    } else {
      info->disabled_reason =
          management::ExtensionInfo::DISABLED_REASON_UNKNOWN;
    }
  }

  if (!ManifestURL::GetUpdateURL(&extension).is_empty()) {
    info->update_url.reset(new std::string(
        ManifestURL::GetUpdateURL(&extension).spec()));
  }

  if (extension.is_app()) {
    info->app_launch_url.reset(new std::string(
        AppLaunchInfo::GetFullLaunchURL(&extension).spec()));
  }

  const ExtensionIconSet::IconMap& icons =
      IconsInfo::GetIcons(&extension).map();
  if (!icons.empty()) {
    info->icons.reset(new IconInfoList());
    ExtensionIconSet::IconMap::const_iterator icon_iter;
    for (icon_iter = icons.begin(); icon_iter != icons.end(); ++icon_iter) {
      management::IconInfo* icon_info = new management::IconInfo();
      icon_info->size = icon_iter->first;
      GURL url = ExtensionIconSource::GetIconURL(
          &extension, icon_info->size, ExtensionIconSet::MATCH_EXACTLY, false,
          NULL);
      icon_info->url = url.spec();
      info->icons->push_back(make_linked_ptr<management::IconInfo>(icon_info));
    }
  }

  const std::set<std::string> perms =
      extension.permissions_data()->active_permissions()->GetAPIsAsStrings();
  if (!perms.empty()) {
    std::set<std::string>::const_iterator perms_iter;
    for (perms_iter = perms.begin(); perms_iter != perms.end(); ++perms_iter)
      info->permissions.push_back(*perms_iter);
  }

  if (!extension.is_hosted_app()) {
    // Skip host permissions for hosted apps.
    const URLPatternSet host_perms =
        extension.permissions_data()->active_permissions()->explicit_hosts();
    if (!host_perms.is_empty()) {
      for (URLPatternSet::const_iterator iter = host_perms.begin();
           iter != host_perms.end(); ++iter) {
        info->host_permissions.push_back(iter->GetAsString());
      }
    }
  }

  switch (extension.location()) {
    case Manifest::INTERNAL:
      info->install_type = management::ExtensionInfo::INSTALL_TYPE_NORMAL;
      break;
    case Manifest::UNPACKED:
    case Manifest::COMMAND_LINE:
      info->install_type = management::ExtensionInfo::INSTALL_TYPE_DEVELOPMENT;
      break;
    case Manifest::EXTERNAL_PREF:
    case Manifest::EXTERNAL_REGISTRY:
    case Manifest::EXTERNAL_PREF_DOWNLOAD:
      info->install_type = management::ExtensionInfo::INSTALL_TYPE_SIDELOAD;
      break;
    case Manifest::EXTERNAL_POLICY:
    case Manifest::EXTERNAL_POLICY_DOWNLOAD:
      info->install_type = management::ExtensionInfo::INSTALL_TYPE_ADMIN;
      break;
    case Manifest::NUM_LOCATIONS:
      NOTREACHED();
    case Manifest::INVALID_LOCATION:
    case Manifest::COMPONENT:
    case Manifest::EXTERNAL_COMPONENT:
      info->install_type = management::ExtensionInfo::INSTALL_TYPE_OTHER;
      break;
  }

  info->launch_type = management::LAUNCH_TYPE_NONE;
  if (extension.is_app()) {
    LaunchType launch_type;
    if (extension.is_platform_app()) {
      launch_type = LAUNCH_TYPE_WINDOW;
    } else {
      launch_type =
          GetLaunchType(ExtensionPrefs::Get(service->profile()), &extension);
    }

    switch (launch_type) {
      case LAUNCH_TYPE_PINNED:
        info->launch_type = management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB;
        break;
      case LAUNCH_TYPE_REGULAR:
        info->launch_type = management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB;
        break;
      case LAUNCH_TYPE_FULLSCREEN:
        info->launch_type = management::LAUNCH_TYPE_OPEN_FULL_SCREEN;
        break;
      case LAUNCH_TYPE_WINDOW:
        info->launch_type = management::LAUNCH_TYPE_OPEN_AS_WINDOW;
        break;
      case LAUNCH_TYPE_INVALID:
      case NUM_LAUNCH_TYPES:
        NOTREACHED();
    }

    info->available_launch_types.reset(new std::vector<management::LaunchType>(
        GetAvailableLaunchTypes(extension)));
  }

  return info.Pass();
}

void AddExtensionInfo(const ExtensionSet& extensions,
                            ExtensionSystem* system,
                            ExtensionInfoList* extension_list,
                            content::BrowserContext* context) {
  for (ExtensionSet::const_iterator iter = extensions.begin();
       iter != extensions.end(); ++iter) {
    const Extension& extension = *iter->get();

    if (ui_util::ShouldNotBeVisible(&extension, context))
      continue;  // Skip built-in extensions/apps.

    extension_list->push_back(make_linked_ptr<management::ExtensionInfo>(
        CreateExtensionInfo(extension, system).release()));
  }
}

}  // namespace

ExtensionService* ManagementFunction::service() {
  return ExtensionSystem::Get(GetProfile())->extension_service();
}

ExtensionService* AsyncManagementFunction::service() {
  return ExtensionSystem::Get(GetProfile())->extension_service();
}

bool ManagementGetAllFunction::RunSync() {
  ExtensionInfoList extensions;
  ExtensionRegistry* registry = ExtensionRegistry::Get(GetProfile());
  ExtensionSystem* system = ExtensionSystem::Get(GetProfile());

  AddExtensionInfo(registry->enabled_extensions(),
                   system, &extensions, browser_context());
  AddExtensionInfo(registry->disabled_extensions(),
                   system, &extensions, browser_context());
  AddExtensionInfo(registry->terminated_extensions(),
                   system, &extensions, browser_context());

  results_ = management::GetAll::Results::Create(extensions);
  return true;
}

bool ManagementGetFunction::RunSync() {
  scoped_ptr<management::Get::Params> params(
      management::Get::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  const Extension* extension = service()->GetExtensionById(params->id, true);
  if (!extension) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
                                                     params->id);
    return false;
  }

  scoped_ptr<management::ExtensionInfo> info =
      CreateExtensionInfo(*extension, ExtensionSystem::Get(GetProfile()));
  results_ = management::Get::Results::Create(*info);

  return true;
}

bool ManagementGetSelfFunction::RunSync() {
  scoped_ptr<management::ExtensionInfo> info =
      CreateExtensionInfo(*extension_, ExtensionSystem::Get(GetProfile()));
  results_ = management::Get::Results::Create(*info);

  return true;
}

bool ManagementGetPermissionWarningsByIdFunction::RunSync() {
  scoped_ptr<management::GetPermissionWarningsById::Params> params(
      management::GetPermissionWarningsById::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  const Extension* extension = service()->GetExtensionById(params->id, true);
  if (!extension) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
                                                     params->id);
    return false;
  }

  std::vector<std::string> warnings = CreateWarningsList(extension);
  results_ = management::GetPermissionWarningsById::Results::Create(warnings);
  return true;
}

namespace {

// This class helps ManagementGetPermissionWarningsByManifestFunction manage
// sending manifest JSON strings to the utility process for parsing.
class SafeManifestJSONParser : public UtilityProcessHostClient {
 public:
  SafeManifestJSONParser(
      ManagementGetPermissionWarningsByManifestFunction* client,
      const std::string& manifest)
      : client_(client),
        manifest_(manifest) {}

  void Start() {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(&SafeManifestJSONParser::StartWorkOnIOThread, this));
  }

  void StartWorkOnIOThread() {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    UtilityProcessHost* host = UtilityProcessHost::Create(
        this, base::MessageLoopProxy::current().get());
    host->Send(new ChromeUtilityMsg_ParseJSON(manifest_));
  }

  virtual bool OnMessageReceived(const IPC::Message& message) override {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(SafeManifestJSONParser, message)
      IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded,
                          OnJSONParseSucceeded)
      IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed,
                          OnJSONParseFailed)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
  }

  void OnJSONParseSucceeded(const base::ListValue& wrapper) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    const base::Value* value = NULL;
    CHECK(wrapper.Get(0, &value));
    if (value->IsType(base::Value::TYPE_DICTIONARY))
      parsed_manifest_.reset(
          static_cast<const base::DictionaryValue*>(value)->DeepCopy());
    else
      error_ = keys::kManifestParseError;

    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&SafeManifestJSONParser::ReportResultFromUIThread, this));
  }

  void OnJSONParseFailed(const std::string& error) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    error_ = error;
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        base::Bind(&SafeManifestJSONParser::ReportResultFromUIThread, this));
  }

  void ReportResultFromUIThread() {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    if (error_.empty() && parsed_manifest_.get())
      client_->OnParseSuccess(parsed_manifest_.Pass());
    else
      client_->OnParseFailure(error_);
  }

 private:
  virtual ~SafeManifestJSONParser() {}

  // The client who we'll report results back to.
  ManagementGetPermissionWarningsByManifestFunction* client_;

  // Data to parse.
  std::string manifest_;

  // Results of parsing.
  scoped_ptr<base::DictionaryValue> parsed_manifest_;

  std::string error_;
};

}  // namespace

bool ManagementGetPermissionWarningsByManifestFunction::RunAsync() {
  scoped_ptr<management::GetPermissionWarningsByManifest::Params> params(
      management::GetPermissionWarningsByManifest::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  scoped_refptr<SafeManifestJSONParser> parser =
      new SafeManifestJSONParser(this, params->manifest_str);
  parser->Start();

  // Matched with a Release() in OnParseSuccess/Failure().
  AddRef();

  // Response is sent async in OnParseSuccess/Failure().
  return true;
}

void ManagementGetPermissionWarningsByManifestFunction::OnParseSuccess(
    scoped_ptr<base::DictionaryValue> parsed_manifest) {
  CHECK(parsed_manifest.get());

  scoped_refptr<Extension> extension = Extension::Create(
      base::FilePath(), Manifest::INVALID_LOCATION, *parsed_manifest,
      Extension::NO_FLAGS, &error_);
  if (!extension.get()) {
    OnParseFailure(keys::kExtensionCreateError);
    return;
  }

  std::vector<std::string> warnings = CreateWarningsList(extension.get());
  results_ =
      management::GetPermissionWarningsByManifest::Results::Create(warnings);
  SendResponse(true);

  // Matched with AddRef() in RunAsync().
  Release();
}

void ManagementGetPermissionWarningsByManifestFunction::OnParseFailure(
    const std::string& error) {
  error_ = error;
  SendResponse(false);

  // Matched with AddRef() in RunAsync().
  Release();
}

bool ManagementLaunchAppFunction::RunSync() {
  scoped_ptr<management::LaunchApp::Params> params(
      management::LaunchApp::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  const Extension* extension = service()->GetExtensionById(params->id, true);
  if (!extension) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
                                                     params->id);
    return false;
  }
  if (!extension->is_app()) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError,
                                                     params->id);
    return false;
  }

  // Look at prefs to find the right launch container.
  // If the user has not set a preference, the default launch value will be
  // returned.
  LaunchContainer launch_container =
      GetLaunchContainer(ExtensionPrefs::Get(GetProfile()), extension);
  OpenApplication(AppLaunchParams(
      GetProfile(), extension, launch_container, NEW_FOREGROUND_TAB));
  CoreAppLauncherHandler::RecordAppLaunchType(
      extension_misc::APP_LAUNCH_EXTENSION_API,
      extension->GetType());

  return true;
}

ManagementSetEnabledFunction::ManagementSetEnabledFunction() {
}

ManagementSetEnabledFunction::~ManagementSetEnabledFunction() {
}

bool ManagementSetEnabledFunction::RunAsync() {
  scoped_ptr<management::SetEnabled::Params> params(
      management::SetEnabled::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  extension_id_ = params->id;

  const Extension* extension =
      ExtensionRegistry::Get(GetProfile())
          ->GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
  if (!extension || ui_util::ShouldNotBeVisible(extension, browser_context())) {
    error_ = ErrorUtils::FormatErrorMessage(
        keys::kNoExtensionError, extension_id_);
    return false;
  }

  const ManagementPolicy* policy =
      ExtensionSystem::Get(GetProfile())->management_policy();
  if (!policy->UserMayModifySettings(extension, NULL) ||
      (!params->enabled && policy->MustRemainEnabled(extension, NULL)) ||
      (params->enabled && policy->MustRemainDisabled(extension, NULL, NULL))) {
    error_ = ErrorUtils::FormatErrorMessage(
        keys::kUserCantModifyError, extension_id_);
    return false;
  }

  bool currently_enabled = service()->IsExtensionEnabled(extension_id_);

  if (!currently_enabled && params->enabled) {
    ExtensionPrefs* prefs = ExtensionPrefs::Get(GetProfile());
    if (prefs->DidExtensionEscalatePermissions(extension_id_)) {
      if (!user_gesture()) {
        error_ = keys::kGestureNeededForEscalationError;
        return false;
      }
      AddRef();  // Matched in InstallUIProceed/InstallUIAbort
      install_prompt_.reset(
          new ExtensionInstallPrompt(GetAssociatedWebContents()));
      install_prompt_->ConfirmReEnable(this, extension);
      return true;
    }
    service()->EnableExtension(extension_id_);
  } else if (currently_enabled && !params->enabled) {
    service()->DisableExtension(extension_id_, Extension::DISABLE_USER_ACTION);
  }

  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&ManagementSetEnabledFunction::SendResponse, this, true));

  return true;
}

void ManagementSetEnabledFunction::InstallUIProceed() {
  service()->EnableExtension(extension_id_);
  SendResponse(true);
  Release();
}

void ManagementSetEnabledFunction::InstallUIAbort(bool user_initiated) {
  error_ = keys::kUserDidNotReEnableError;
  SendResponse(false);
  Release();
}

ManagementUninstallFunctionBase::ManagementUninstallFunctionBase() {
}

ManagementUninstallFunctionBase::~ManagementUninstallFunctionBase() {
}

bool ManagementUninstallFunctionBase::Uninstall(
    const std::string& target_extension_id,
    bool show_confirm_dialog) {
  extension_id_ = target_extension_id;
  const Extension* target_extension =
      extensions::ExtensionRegistry::Get(browser_context())->
          GetExtensionById(extension_id_, ExtensionRegistry::EVERYTHING);
  if (!target_extension ||
      ui_util::ShouldNotBeVisible(target_extension, browser_context())) {
    error_ = ErrorUtils::FormatErrorMessage(
        keys::kNoExtensionError, extension_id_);
    return false;
  }

  if (!ExtensionSystem::Get(GetProfile())
           ->management_policy()
           ->UserMayModifySettings(target_extension, NULL)) {
    error_ = ErrorUtils::FormatErrorMessage(
        keys::kUserCantModifyError, extension_id_);
    return false;
  }

  if (auto_confirm_for_test == DO_NOT_SKIP) {
    if (show_confirm_dialog) {
      AddRef();  // Balanced in ExtensionUninstallAccepted/Canceled
      extensions::WindowController* controller = GetExtensionWindowController();
      extension_uninstall_dialog_.reset(ExtensionUninstallDialog::Create(
          GetProfile(),
          controller ? controller->window()->GetNativeWindow() : NULL,
          this));
      if (extension_id() != target_extension_id) {
        extension_uninstall_dialog_->ConfirmProgrammaticUninstall(
            target_extension, extension());
      } else {
        // If this is a self uninstall, show the generic uninstall dialog.
        extension_uninstall_dialog_->ConfirmUninstall(target_extension);
      }
    } else {
      Finish(true);
    }
  } else {
    Finish(auto_confirm_for_test == PROCEED);
  }

  return true;
}

// static
void ManagementUninstallFunctionBase::SetAutoConfirmForTest(
    bool should_proceed) {
  auto_confirm_for_test = should_proceed ? PROCEED : ABORT;
}

void ManagementUninstallFunctionBase::Finish(bool should_uninstall) {
  if (should_uninstall) {
    // The extension can be uninstalled in another window while the UI was
    // showing. Do nothing in that case.
    ExtensionRegistry* registry = ExtensionRegistry::Get(GetProfile());
    const Extension* extension = registry->GetExtensionById(
        extension_id_, ExtensionRegistry::EVERYTHING);
    if (!extension) {
      error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
                                              extension_id_);
      SendResponse(false);
    } else {
      bool success = service()->UninstallExtension(
          extension_id_,
          extensions::UNINSTALL_REASON_MANAGEMENT_API,
          base::Bind(&base::DoNothing),
          NULL);

      // TODO set error_ if !success
      SendResponse(success);
    }
  } else {
    error_ = ErrorUtils::FormatErrorMessage(
        keys::kUninstallCanceledError, extension_id_);
    SendResponse(false);
  }
}

void ManagementUninstallFunctionBase::ExtensionUninstallAccepted() {
  Finish(true);
  Release();
}

void ManagementUninstallFunctionBase::ExtensionUninstallCanceled() {
  Finish(false);
  Release();
}

ManagementUninstallFunction::ManagementUninstallFunction() {
}

ManagementUninstallFunction::~ManagementUninstallFunction() {
}

bool ManagementUninstallFunction::RunAsync() {
  scoped_ptr<management::Uninstall::Params> params(
      management::Uninstall::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(extension_.get());
  EXTENSION_FUNCTION_VALIDATE(params.get());

  bool show_confirm_dialog = true;
  // By default confirmation dialog isn't shown when uninstalling self, but this
  // can be overridden with showConfirmDialog.
  if (params->id == extension_->id()) {
    show_confirm_dialog = params->options.get() &&
                          params->options->show_confirm_dialog.get() &&
                          *params->options->show_confirm_dialog;
  }
  if (show_confirm_dialog && !user_gesture()) {
    error_ = keys::kGestureNeededForUninstallError;
    return false;
  }
  return Uninstall(params->id, show_confirm_dialog);
}

ManagementUninstallSelfFunction::ManagementUninstallSelfFunction() {
}

ManagementUninstallSelfFunction::~ManagementUninstallSelfFunction() {
}

bool ManagementUninstallSelfFunction::RunAsync() {
  scoped_ptr<management::UninstallSelf::Params> params(
      management::UninstallSelf::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  bool show_confirm_dialog = false;
  if (params->options.get() && params->options->show_confirm_dialog.get())
    show_confirm_dialog = *params->options->show_confirm_dialog;
  return Uninstall(extension_->id(), show_confirm_dialog);
}

ManagementCreateAppShortcutFunction::ManagementCreateAppShortcutFunction() {
}

ManagementCreateAppShortcutFunction::~ManagementCreateAppShortcutFunction() {
}

// static
void ManagementCreateAppShortcutFunction::SetAutoConfirmForTest(
    bool should_proceed) {
  auto_confirm_for_test = should_proceed ? PROCEED : ABORT;
}

void ManagementCreateAppShortcutFunction::OnCloseShortcutPrompt(bool created) {
  if (!created)
    error_ = keys::kCreateShortcutCanceledError;
  SendResponse(created);
  Release();
}

bool ManagementCreateAppShortcutFunction::RunAsync() {
  if (!user_gesture()) {
    error_ = keys::kGestureNeededForCreateAppShortcutError;
    return false;
  }

  scoped_ptr<management::CreateAppShortcut::Params> params(
      management::CreateAppShortcut::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  const Extension* extension = service()->GetExtensionById(params->id, true);
  if (!extension) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNoExtensionError,
                                            params->id);
    return false;
  }

  if (!extension->is_app()) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id);
    return false;
  }

#if defined(OS_MACOSX)
  if (!extension->is_platform_app()) {
    error_ = keys::kCreateOnlyPackagedAppShortcutMac;
    return false;
  }
#endif

  Browser* browser = chrome::FindBrowserWithProfile(
      GetProfile(), chrome::HOST_DESKTOP_TYPE_NATIVE);
  if (!browser) {
    // Shouldn't happen if we have user gesture.
    error_ = keys::kNoBrowserToCreateShortcut;
    return false;
  }

  // Matched with a Release() in OnCloseShortcutPrompt().
  AddRef();

  if (auto_confirm_for_test == DO_NOT_SKIP) {
    chrome::ShowCreateChromeAppShortcutsDialog(
        browser->window()->GetNativeWindow(), browser->profile(), extension,
        base::Bind(&ManagementCreateAppShortcutFunction::OnCloseShortcutPrompt,
           this));
  } else {
    OnCloseShortcutPrompt(auto_confirm_for_test == PROCEED);
  }

  // Response is sent async in OnCloseShortcutPrompt().
  return true;
}

bool ManagementSetLaunchTypeFunction::RunSync() {
  if (!user_gesture()) {
    error_ = keys::kGestureNeededForSetLaunchTypeError;
    return false;
  }

  scoped_ptr<management::SetLaunchType::Params> params(
      management::SetLaunchType::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());
  const Extension* extension = service()->GetExtensionById(params->id, true);
  if (!extension) {
    error_ =
        ErrorUtils::FormatErrorMessage(keys::kNoExtensionError, params->id);
    return false;
  }

  if (!extension->is_app()) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kNotAnAppError, params->id);
    return false;
  }

  std::vector<management::LaunchType> available_launch_types =
      GetAvailableLaunchTypes(*extension);

  management::LaunchType app_launch_type = params->launch_type;
  if (std::find(available_launch_types.begin(),
                available_launch_types.end(),
                app_launch_type) == available_launch_types.end()) {
    error_ = keys::kLaunchTypeNotAvailableError;
    return false;
  }

  LaunchType launch_type = LAUNCH_TYPE_DEFAULT;
  switch (app_launch_type) {
    case management::LAUNCH_TYPE_OPEN_AS_PINNED_TAB:
      launch_type = LAUNCH_TYPE_PINNED;
      break;
    case management::LAUNCH_TYPE_OPEN_AS_REGULAR_TAB:
      launch_type = LAUNCH_TYPE_REGULAR;
      break;
    case management::LAUNCH_TYPE_OPEN_FULL_SCREEN:
      launch_type = LAUNCH_TYPE_FULLSCREEN;
      break;
    case management::LAUNCH_TYPE_OPEN_AS_WINDOW:
      launch_type = LAUNCH_TYPE_WINDOW;
      break;
    case management::LAUNCH_TYPE_NONE:
      NOTREACHED();
  }

  SetLaunchType(service(), params->id, launch_type);

  return true;
}

ManagementGenerateAppForLinkFunction::ManagementGenerateAppForLinkFunction() {
}

ManagementGenerateAppForLinkFunction::~ManagementGenerateAppForLinkFunction() {
}

void ManagementGenerateAppForLinkFunction::FinishCreateBookmarkApp(
    const Extension* extension,
    const WebApplicationInfo& web_app_info) {
  if (extension) {
    scoped_ptr<management::ExtensionInfo> info =
        CreateExtensionInfo(*extension, ExtensionSystem::Get(GetProfile()));
    results_ = management::GenerateAppForLink::Results::Create(*info);

    SendResponse(true);
    Release();
  } else {
    error_ = keys::kGenerateAppForLinkInstallError;
    SendResponse(false);
    Release();
  }
}

void ManagementGenerateAppForLinkFunction::OnFaviconForApp(
    const favicon_base::FaviconImageResult& image_result) {
  WebApplicationInfo web_app;
  web_app.title = base::UTF8ToUTF16(title_);
  web_app.app_url = launch_url_;

  if (!image_result.image.IsEmpty()) {
    WebApplicationInfo::IconInfo icon;
    icon.data = image_result.image.AsBitmap();
    icon.width = icon.data.width();
    icon.height = icon.data.height();
    web_app.icons.push_back(icon);
  }

  bookmark_app_helper_.reset(new BookmarkAppHelper(service(), web_app, NULL));
  bookmark_app_helper_->Create(base::Bind(
      &ManagementGenerateAppForLinkFunction::FinishCreateBookmarkApp, this));
}

bool ManagementGenerateAppForLinkFunction::RunAsync() {
  if (!user_gesture()) {
    error_ = keys::kGestureNeededForGenerateAppForLinkError;
    return false;
  }

  scoped_ptr<management::GenerateAppForLink::Params> params(
      management::GenerateAppForLink::Params::Create(*args_));
  EXTENSION_FUNCTION_VALIDATE(params.get());

  GURL launch_url(params->url);
  if (!launch_url.is_valid() || !launch_url.SchemeIsHTTPOrHTTPS()) {
    error_ = ErrorUtils::FormatErrorMessage(keys::kInvalidURLError,
                                            params->url);
    return false;
  }

  if (params->title.empty()) {
    error_ = keys::kEmptyTitleError;
    return false;
  }

  FaviconService* favicon_service =
      FaviconServiceFactory::GetForProfile(GetProfile(),
                                           Profile::EXPLICIT_ACCESS);
  DCHECK(favicon_service);

  title_ = params->title;
  launch_url_ = launch_url;

  favicon_service->GetFaviconImageForPageURL(
      launch_url,
      base::Bind(&ManagementGenerateAppForLinkFunction::OnFaviconForApp, this),
      &cancelable_task_tracker_);

  // Matched with a Release() in OnExtensionLoaded().
  AddRef();

  // Response is sent async in OnExtensionLoaded().
  return true;
}

ManagementEventRouter::ManagementEventRouter(content::BrowserContext* context)
    : browser_context_(context), extension_registry_observer_(this) {
  extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
}

ManagementEventRouter::~ManagementEventRouter() {}

void ManagementEventRouter::OnExtensionLoaded(
    content::BrowserContext* browser_context,
    const Extension* extension) {
  BroadcastEvent(extension, management::OnEnabled::kEventName);
}

void ManagementEventRouter::OnExtensionUnloaded(
    content::BrowserContext* browser_context,
    const Extension* extension,
    UnloadedExtensionInfo::Reason reason) {
  BroadcastEvent(extension, management::OnDisabled::kEventName);
}

void ManagementEventRouter::OnExtensionInstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    bool is_update) {
  BroadcastEvent(extension, management::OnInstalled::kEventName);
}

void ManagementEventRouter::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const Extension* extension,
    extensions::UninstallReason reason) {
  BroadcastEvent(extension, management::OnUninstalled::kEventName);
}

void ManagementEventRouter::BroadcastEvent(const Extension* extension,
                                           const char* event_name) {
  if (ui_util::ShouldNotBeVisible(extension, browser_context_))
    return;  // Don't dispatch events for built-in extenions.
  scoped_ptr<base::ListValue> args(new base::ListValue());
  if (event_name == management::OnUninstalled::kEventName) {
    args->Append(new base::StringValue(extension->id()));
  } else {
    scoped_ptr<management::ExtensionInfo> info =
        CreateExtensionInfo(*extension, ExtensionSystem::Get(browser_context_));
    args->Append(info->ToValue().release());
  }

  EventRouter::Get(browser_context_)
      ->BroadcastEvent(scoped_ptr<Event>(new Event(event_name, args.Pass())));
}

ManagementAPI::ManagementAPI(content::BrowserContext* context)
    : browser_context_(context) {
  EventRouter* event_router = EventRouter::Get(browser_context_);
  event_router->RegisterObserver(this, management::OnInstalled::kEventName);
  event_router->RegisterObserver(this, management::OnUninstalled::kEventName);
  event_router->RegisterObserver(this, management::OnEnabled::kEventName);
  event_router->RegisterObserver(this, management::OnDisabled::kEventName);
}

ManagementAPI::~ManagementAPI() {
}

void ManagementAPI::Shutdown() {
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<ManagementAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<ManagementAPI>*
ManagementAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

void ManagementAPI::OnListenerAdded(const EventListenerInfo& details) {
  // TODO(vadimt): Remove ScopedProfile below once crbug.com/417106 is fixed.
  tracked_objects::ScopedProfile tracking_profile(
      FROM_HERE_WITH_EXPLICIT_FUNCTION("ManagementAPI::OnListenerAdded"));

  management_event_router_.reset(new ManagementEventRouter(browser_context_));
  EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

}  // namespace extensions