普通文本  |  389行  |  13.28 KB

// Copyright 2013 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/apps/ephemeral_app_service.h"

#include "apps/app_lifetime_monitor_factory.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "chrome/browser/apps/ephemeral_app_service_factory.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/uninstall_reason.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"

using extensions::Extension;
using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry;
using extensions::ExtensionSet;
using extensions::ExtensionSystem;

namespace {

// The number of seconds after startup before performing garbage collection
// of ephemeral apps.
const int kGarbageCollectAppsStartupDelay = 60;

// The number of seconds after an ephemeral app has been installed before
// performing garbage collection.
const int kGarbageCollectAppsInstallDelay = 15;

// When the number of ephemeral apps reaches this count, trigger garbage
// collection to trim off the least-recently used apps in excess of
// kMaxEphemeralAppsCount.
const int kGarbageCollectAppsTriggerCount = 35;

// The number of seconds after an app has stopped running before it will be
// disabled.
const int kDefaultDisableAppDelay = 1;

// The number of seconds after startup before disabling inactive ephemeral apps.
const int kDisableAppsOnStartupDelay = 5;

}  // namespace

const int EphemeralAppService::kAppInactiveThreshold = 10;
const int EphemeralAppService::kAppKeepThreshold = 1;
const int EphemeralAppService::kMaxEphemeralAppsCount = 30;

// static
EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
  return EphemeralAppServiceFactory::GetForProfile(profile);
}

EphemeralAppService::EphemeralAppService(Profile* profile)
    : profile_(profile),
      extension_registry_observer_(this),
      app_lifetime_monitor_observer_(this),
      ephemeral_app_count_(-1),
      disable_idle_app_delay_(kDefaultDisableAppDelay),
      weak_ptr_factory_(this) {
  registrar_.Add(this,
                 extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
                 content::Source<Profile>(profile_));
}

EphemeralAppService::~EphemeralAppService() {
}

void EphemeralAppService::ClearCachedApps() {
  // Cancel any pending garbage collects.
  garbage_collect_apps_timer_.Stop();

  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
  DCHECK(registry);
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
  DCHECK(prefs);
  ExtensionService* service =
      ExtensionSystem::Get(profile_)->extension_service();
  DCHECK(service);

  scoped_ptr<ExtensionSet> extensions =
      registry->GenerateInstalledExtensionsSet();

  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end();
       ++it) {
    std::string extension_id = (*it)->id();
    if (!prefs->IsEphemeralApp(extension_id))
      continue;

    // Do not remove apps that are running.
    if (!extensions::util::IsExtensionIdle(extension_id, profile_))
      continue;

    DCHECK(registry->GetExtensionById(extension_id,
                                      ExtensionRegistry::EVERYTHING));
    service->UninstallExtension(
        extension_id,
        extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
        base::Bind(&base::DoNothing),
        NULL);
  }
}

void EphemeralAppService::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  switch (type) {
    case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
      Init();
      break;
    }
    default:
      NOTREACHED();
  }
}

void EphemeralAppService::OnExtensionWillBeInstalled(
    content::BrowserContext* browser_context,
    const extensions::Extension* extension,
    bool is_update,
    bool from_ephemeral,
    const std::string& old_name) {
  if (from_ephemeral) {
    // An ephemeral app was just promoted to a regular installed app.
    --ephemeral_app_count_;
    DCHECK_GE(ephemeral_app_count_, 0);
    HandleEphemeralAppPromoted(extension);
  } else if (!is_update &&
             extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    // A new ephemeral app was launched.
    ++ephemeral_app_count_;
    if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
      TriggerGarbageCollect(
          base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
    }
  }
}

void EphemeralAppService::OnExtensionUninstalled(
    content::BrowserContext* browser_context,
    const extensions::Extension* extension,
    extensions::UninstallReason reason) {
  if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
    --ephemeral_app_count_;
    DCHECK_GE(ephemeral_app_count_, 0);
  }
}

void EphemeralAppService::OnAppStop(Profile* profile,
                                    const std::string& app_id) {
  if (!extensions::util::IsEphemeralApp(app_id, profile_))
    return;

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&EphemeralAppService::DisableEphemeralApp,
                 weak_ptr_factory_.GetWeakPtr(),
                 app_id),
      base::TimeDelta::FromSeconds(disable_idle_app_delay_));
}

void EphemeralAppService::OnChromeTerminating() {
  garbage_collect_apps_timer_.Stop();

  extension_registry_observer_.RemoveAll();
  app_lifetime_monitor_observer_.RemoveAll();
}

void EphemeralAppService::Init() {
  InitEphemeralAppCount();

  // Start observing.
  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
  app_lifetime_monitor_observer_.Add(
      apps::AppLifetimeMonitorFactory::GetForProfile(profile_));

  // Execute startup clean up tasks (except during tests).
  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
    return;

  TriggerGarbageCollect(
      base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));

  base::MessageLoop::current()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
                 weak_ptr_factory_.GetWeakPtr()),
      base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
}

void EphemeralAppService::InitEphemeralAppCount() {
  scoped_ptr<ExtensionSet> extensions =
      ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
  DCHECK(prefs);

  ephemeral_app_count_ = 0;
  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end(); ++it) {
    const Extension* extension = it->get();
    if (prefs->IsEphemeralApp(extension->id()))
      ++ephemeral_app_count_;
  }
}

void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
  if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
      !extensions::util::IsExtensionIdle(app_id, profile_)) {
    return;
  }

  // After an ephemeral app has stopped running, unload it from extension
  // system and disable it to prevent all background activity.
  ExtensionService* service =
      ExtensionSystem::Get(profile_)->extension_service();
  DCHECK(service);
  service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
}

void EphemeralAppService::DisableEphemeralAppsOnStartup() {
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
  DCHECK(prefs);
  ExtensionService* service =
      ExtensionSystem::Get(profile_)->extension_service();
  DCHECK(service);

  // Ensure that all inactive ephemeral apps are disabled to prevent all
  // background activity. This is done on startup to catch any apps that escaped
  // being disabled on shutdown.
  scoped_ptr<ExtensionSet> extensions =
      ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end();
       ++it) {
    const Extension* extension = it->get();
    if (!prefs->IsEphemeralApp(extension->id()))
      continue;

    // Only V2 apps are installed ephemerally. Remove other ephemeral app types
    // that were cached before this policy was introduced.
    if (!extension->is_platform_app()) {
      service->UninstallExtension(
          extension->id(),
          extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
          base::Bind(&base::DoNothing),
          NULL);
      continue;
    }

    if (!prefs->HasDisableReason(extension->id(),
                                 Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
        !prefs->IsExtensionRunning(extension->id()) &&
        extensions::util::IsExtensionIdle(extension->id(), profile_)) {
      service->DisableExtension(extension->id(),
                                Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
    }
  }
}

void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
  // When ephemeral apps are promoted to regular install apps, remove the
  // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
  // other reasons.
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
  DCHECK(prefs);

  int disable_reasons = prefs->GetDisableReasons(app->id());
  if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
    prefs->RemoveDisableReason(app->id(),
                               Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
    if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP)
      prefs->SetExtensionState(app->id(), Extension::ENABLED);
  }
}

void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
  if (!garbage_collect_apps_timer_.IsRunning()) {
    garbage_collect_apps_timer_.Start(
        FROM_HERE,
        delay,
        this,
        &EphemeralAppService::GarbageCollectApps);
  }
}

void EphemeralAppService::GarbageCollectApps() {
  ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
  DCHECK(registry);
  ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
  DCHECK(prefs);

  scoped_ptr<ExtensionSet> extensions =
      registry->GenerateInstalledExtensionsSet();

  int app_count = 0;
  LaunchTimeAppMap app_launch_times;
  std::set<std::string> remove_app_ids;

  // Populate a list of ephemeral apps, ordered by their last launch time.
  for (ExtensionSet::const_iterator it = extensions->begin();
       it != extensions->end(); ++it) {
    const Extension* extension = it->get();
    if (!prefs->IsEphemeralApp(extension->id()))
      continue;

    ++app_count;

    // Do not remove ephemeral apps that are running.
    if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
      continue;

    base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());

    // If the last launch time is invalid, this may be because it was just
    // installed. So use the install time. If this is also null for some reason,
    // the app will be removed.
    if (last_launch_time.is_null())
      last_launch_time = prefs->GetInstallTime(extension->id());

    app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
  }

  ExtensionService* service =
      ExtensionSystem::Get(profile_)->extension_service();
  DCHECK(service);
  // Execute the eviction policies and remove apps marked for deletion.
  if (!app_launch_times.empty()) {
    GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);

    for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
         id != remove_app_ids.end(); ++id) {
      // Protect against cascading uninstalls.
      if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING))
        continue;

      service->UninstallExtension(
          *id,
          extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
          base::Bind(&base::DoNothing),
          NULL);
    }
  }
}

// static
void EphemeralAppService::GetAppsToRemove(
    int app_count,
    const LaunchTimeAppMap& app_launch_times,
    std::set<std::string>* remove_app_ids) {
  base::Time time_now = base::Time::Now();
  const base::Time inactive_threshold =
      time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
  const base::Time keep_threshold =
      time_now - base::TimeDelta::FromDays(kAppKeepThreshold);

  // Visit the apps in order of least recently to most recently launched.
  for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
       it != app_launch_times.end(); ++it) {
    // Cannot remove apps that have been launched recently. So break when we
    // reach the new apps.
    if (it->first > keep_threshold)
        break;

    // Remove ephemeral apps that have been inactive for a while or if the cache
    // is larger than the desired size.
    if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
      remove_app_ids->insert(it->second);
      --app_count;
    } else {
      break;
    }
  }
}