Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / apps / ephemeral_app_service.cc
blob584205f7217da0277994f0b7b57793124cfdac42
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/apps/ephemeral_app_service.h"
7 #include "apps/app_lifetime_monitor_factory.h"
8 #include "base/command_line.h"
9 #include "base/location.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/apps/ephemeral_app_service_factory.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/extension_util.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "extensions/browser/extension_prefs.h"
18 #include "extensions/browser/extension_registry.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/browser/extension_util.h"
21 #include "extensions/browser/uninstall_reason.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_set.h"
24 #include "extensions/common/one_shot_event.h"
26 using extensions::Extension;
27 using extensions::ExtensionPrefs;
28 using extensions::ExtensionRegistry;
29 using extensions::ExtensionSet;
30 using extensions::ExtensionSystem;
32 namespace {
34 // The number of seconds after startup before performing garbage collection
35 // of ephemeral apps.
36 const int kGarbageCollectAppsStartupDelay = 60;
38 // The number of seconds after an ephemeral app has been installed before
39 // performing garbage collection.
40 const int kGarbageCollectAppsInstallDelay = 15;
42 // When the number of ephemeral apps reaches this count, trigger garbage
43 // collection to trim off the least-recently used apps in excess of
44 // kMaxEphemeralAppsCount.
45 const int kGarbageCollectAppsTriggerCount = 35;
47 // The number of seconds after an app has stopped running before it will be
48 // disabled.
49 const int kDefaultDisableAppDelay = 1;
51 // The number of seconds after startup before disabling inactive ephemeral apps.
52 const int kDisableAppsOnStartupDelay = 5;
54 } // namespace
56 const int EphemeralAppService::kAppInactiveThreshold = 10;
57 const int EphemeralAppService::kAppKeepThreshold = 1;
58 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
60 // static
61 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
62 return EphemeralAppServiceFactory::GetForProfile(profile);
65 EphemeralAppService::EphemeralAppService(Profile* profile)
66 : profile_(profile),
67 extension_registry_observer_(this),
68 app_lifetime_monitor_observer_(this),
69 ephemeral_app_count_(-1),
70 disable_idle_app_delay_(kDefaultDisableAppDelay),
71 weak_ptr_factory_(this) {
72 ExtensionSystem::Get(profile_)->ready().Post(
73 FROM_HERE,
74 base::Bind(&EphemeralAppService::Init, weak_ptr_factory_.GetWeakPtr()));
77 EphemeralAppService::~EphemeralAppService() {
80 void EphemeralAppService::ClearCachedApps() {
81 // Cancel any pending garbage collects.
82 garbage_collect_apps_timer_.Stop();
84 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
85 DCHECK(registry);
86 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
87 DCHECK(prefs);
88 ExtensionService* service =
89 ExtensionSystem::Get(profile_)->extension_service();
90 DCHECK(service);
92 scoped_ptr<ExtensionSet> extensions =
93 registry->GenerateInstalledExtensionsSet();
95 for (ExtensionSet::const_iterator it = extensions->begin();
96 it != extensions->end();
97 ++it) {
98 std::string extension_id = (*it)->id();
99 if (!prefs->IsEphemeralApp(extension_id))
100 continue;
102 // Do not remove apps that are running.
103 if (!extensions::util::IsExtensionIdle(extension_id, profile_))
104 continue;
106 DCHECK(registry->GetExtensionById(extension_id,
107 ExtensionRegistry::EVERYTHING));
108 service->UninstallExtension(
109 extension_id,
110 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
111 base::Bind(&base::DoNothing),
112 NULL);
116 void EphemeralAppService::OnExtensionWillBeInstalled(
117 content::BrowserContext* browser_context,
118 const extensions::Extension* extension,
119 bool is_update,
120 bool from_ephemeral,
121 const std::string& old_name) {
122 if (from_ephemeral) {
123 // An ephemeral app was just promoted to a regular installed app.
124 --ephemeral_app_count_;
125 DCHECK_GE(ephemeral_app_count_, 0);
126 HandleEphemeralAppPromoted(extension);
127 } else if (!is_update &&
128 extensions::util::IsEphemeralApp(extension->id(), profile_)) {
129 // A new ephemeral app was launched.
130 ++ephemeral_app_count_;
131 if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
132 TriggerGarbageCollect(
133 base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
138 void EphemeralAppService::OnExtensionUninstalled(
139 content::BrowserContext* browser_context,
140 const extensions::Extension* extension,
141 extensions::UninstallReason reason) {
142 if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
143 --ephemeral_app_count_;
144 DCHECK_GE(ephemeral_app_count_, 0);
148 void EphemeralAppService::OnAppStop(Profile* profile,
149 const std::string& app_id) {
150 if (!extensions::util::IsEphemeralApp(app_id, profile_))
151 return;
153 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
154 FROM_HERE, base::Bind(&EphemeralAppService::DisableEphemeralApp,
155 weak_ptr_factory_.GetWeakPtr(), app_id),
156 base::TimeDelta::FromSeconds(disable_idle_app_delay_));
159 void EphemeralAppService::OnChromeTerminating() {
160 garbage_collect_apps_timer_.Stop();
162 extension_registry_observer_.RemoveAll();
163 app_lifetime_monitor_observer_.RemoveAll();
166 void EphemeralAppService::Init() {
167 InitEphemeralAppCount();
169 // Start observing.
170 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
171 app_lifetime_monitor_observer_.Add(
172 apps::AppLifetimeMonitorFactory::GetForProfile(profile_));
174 // Execute startup clean up tasks (except during tests).
175 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
176 return;
178 TriggerGarbageCollect(
179 base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
181 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
182 FROM_HERE, base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
183 weak_ptr_factory_.GetWeakPtr()),
184 base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
187 void EphemeralAppService::InitEphemeralAppCount() {
188 scoped_ptr<ExtensionSet> extensions =
189 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
190 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
191 DCHECK(prefs);
193 ephemeral_app_count_ = 0;
194 for (ExtensionSet::const_iterator it = extensions->begin();
195 it != extensions->end(); ++it) {
196 const Extension* extension = it->get();
197 if (prefs->IsEphemeralApp(extension->id()))
198 ++ephemeral_app_count_;
202 void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
203 if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
204 !extensions::util::IsExtensionIdle(app_id, profile_)) {
205 return;
208 // After an ephemeral app has stopped running, unload it from extension
209 // system and disable it to prevent all background activity.
210 ExtensionService* service =
211 ExtensionSystem::Get(profile_)->extension_service();
212 DCHECK(service);
213 service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
216 void EphemeralAppService::DisableEphemeralAppsOnStartup() {
217 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
218 DCHECK(prefs);
219 ExtensionService* service =
220 ExtensionSystem::Get(profile_)->extension_service();
221 DCHECK(service);
223 // Ensure that all inactive ephemeral apps are disabled to prevent all
224 // background activity. This is done on startup to catch any apps that escaped
225 // being disabled on shutdown.
226 scoped_ptr<ExtensionSet> extensions =
227 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
228 for (ExtensionSet::const_iterator it = extensions->begin();
229 it != extensions->end();
230 ++it) {
231 const Extension* extension = it->get();
232 if (!prefs->IsEphemeralApp(extension->id()))
233 continue;
235 // Only V2 apps are installed ephemerally. Remove other ephemeral app types
236 // that were cached before this policy was introduced.
237 if (!extension->is_platform_app()) {
238 service->UninstallExtension(
239 extension->id(),
240 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
241 base::Bind(&base::DoNothing),
242 NULL);
243 continue;
246 if (!prefs->HasDisableReason(extension->id(),
247 Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
248 !prefs->IsExtensionRunning(extension->id()) &&
249 extensions::util::IsExtensionIdle(extension->id(), profile_)) {
250 service->DisableExtension(extension->id(),
251 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
256 void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
257 // When ephemeral apps are promoted to regular install apps, remove the
258 // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
259 // other reasons.
260 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
261 DCHECK(prefs);
263 int disable_reasons = prefs->GetDisableReasons(app->id());
264 if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
265 if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
266 // This will also clear disable reasons.
267 prefs->SetExtensionEnabled(app->id());
268 } else {
269 prefs->RemoveDisableReason(app->id(),
270 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
275 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
276 if (!garbage_collect_apps_timer_.IsRunning()) {
277 garbage_collect_apps_timer_.Start(
278 FROM_HERE,
279 delay,
280 this,
281 &EphemeralAppService::GarbageCollectApps);
285 void EphemeralAppService::GarbageCollectApps() {
286 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
287 DCHECK(registry);
288 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
289 DCHECK(prefs);
291 scoped_ptr<ExtensionSet> extensions =
292 registry->GenerateInstalledExtensionsSet();
294 int app_count = 0;
295 LaunchTimeAppMap app_launch_times;
296 std::set<std::string> remove_app_ids;
298 // Populate a list of ephemeral apps, ordered by their last launch time.
299 for (ExtensionSet::const_iterator it = extensions->begin();
300 it != extensions->end(); ++it) {
301 const Extension* extension = it->get();
302 if (!prefs->IsEphemeralApp(extension->id()))
303 continue;
305 ++app_count;
307 // Do not remove ephemeral apps that are running.
308 if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
309 continue;
311 base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
313 // If the last launch time is invalid, this may be because it was just
314 // installed. So use the install time. If this is also null for some reason,
315 // the app will be removed.
316 if (last_launch_time.is_null())
317 last_launch_time = prefs->GetInstallTime(extension->id());
319 app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
322 ExtensionService* service =
323 ExtensionSystem::Get(profile_)->extension_service();
324 DCHECK(service);
325 // Execute the eviction policies and remove apps marked for deletion.
326 if (!app_launch_times.empty()) {
327 GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
329 for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
330 id != remove_app_ids.end(); ++id) {
331 // Protect against cascading uninstalls.
332 if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING))
333 continue;
335 service->UninstallExtension(
336 *id,
337 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
338 base::Bind(&base::DoNothing),
339 NULL);
344 // static
345 void EphemeralAppService::GetAppsToRemove(
346 int app_count,
347 const LaunchTimeAppMap& app_launch_times,
348 std::set<std::string>* remove_app_ids) {
349 base::Time time_now = base::Time::Now();
350 const base::Time inactive_threshold =
351 time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
352 const base::Time keep_threshold =
353 time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
355 // Visit the apps in order of least recently to most recently launched.
356 for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
357 it != app_launch_times.end(); ++it) {
358 // Cannot remove apps that have been launched recently. So break when we
359 // reach the new apps.
360 if (it->first > keep_threshold)
361 break;
363 // Remove ephemeral apps that have been inactive for a while or if the cache
364 // is larger than the desired size.
365 if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
366 remove_app_ids->insert(it->second);
367 --app_count;
368 } else {
369 break;