blob: 36c8cdab4a9c7bc7da5201c26f34d09d6533985e [file] [log] [blame]
// 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;
}
}
}