Use app list item shadow for app list folders.
[chromium-blink-merge.git] / chrome / browser / apps / ephemeral_app_service.cc
blobd5ff5b4b094eb8aeca1d8adad66e664bd5ec0887
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/message_loop/message_loop.h"
10 #include "chrome/browser/apps/ephemeral_app_service_factory.h"
11 #include "chrome/browser/extensions/extension_service.h"
12 #include "chrome/browser/extensions/extension_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/chrome_switches.h"
15 #include "extensions/browser/extension_prefs.h"
16 #include "extensions/browser/extension_registry.h"
17 #include "extensions/browser/extension_system.h"
18 #include "extensions/browser/extension_util.h"
19 #include "extensions/browser/uninstall_reason.h"
20 #include "extensions/common/extension.h"
21 #include "extensions/common/extension_set.h"
22 #include "extensions/common/one_shot_event.h"
24 using extensions::Extension;
25 using extensions::ExtensionPrefs;
26 using extensions::ExtensionRegistry;
27 using extensions::ExtensionSet;
28 using extensions::ExtensionSystem;
30 namespace {
32 // The number of seconds after startup before performing garbage collection
33 // of ephemeral apps.
34 const int kGarbageCollectAppsStartupDelay = 60;
36 // The number of seconds after an ephemeral app has been installed before
37 // performing garbage collection.
38 const int kGarbageCollectAppsInstallDelay = 15;
40 // When the number of ephemeral apps reaches this count, trigger garbage
41 // collection to trim off the least-recently used apps in excess of
42 // kMaxEphemeralAppsCount.
43 const int kGarbageCollectAppsTriggerCount = 35;
45 // The number of seconds after an app has stopped running before it will be
46 // disabled.
47 const int kDefaultDisableAppDelay = 1;
49 // The number of seconds after startup before disabling inactive ephemeral apps.
50 const int kDisableAppsOnStartupDelay = 5;
52 } // namespace
54 const int EphemeralAppService::kAppInactiveThreshold = 10;
55 const int EphemeralAppService::kAppKeepThreshold = 1;
56 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
58 // static
59 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
60 return EphemeralAppServiceFactory::GetForProfile(profile);
63 EphemeralAppService::EphemeralAppService(Profile* profile)
64 : profile_(profile),
65 extension_registry_observer_(this),
66 app_lifetime_monitor_observer_(this),
67 ephemeral_app_count_(-1),
68 disable_idle_app_delay_(kDefaultDisableAppDelay),
69 weak_ptr_factory_(this) {
70 ExtensionSystem::Get(profile_)->ready().Post(
71 FROM_HERE,
72 base::Bind(&EphemeralAppService::Init, weak_ptr_factory_.GetWeakPtr()));
75 EphemeralAppService::~EphemeralAppService() {
78 void EphemeralAppService::ClearCachedApps() {
79 // Cancel any pending garbage collects.
80 garbage_collect_apps_timer_.Stop();
82 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
83 DCHECK(registry);
84 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
85 DCHECK(prefs);
86 ExtensionService* service =
87 ExtensionSystem::Get(profile_)->extension_service();
88 DCHECK(service);
90 scoped_ptr<ExtensionSet> extensions =
91 registry->GenerateInstalledExtensionsSet();
93 for (ExtensionSet::const_iterator it = extensions->begin();
94 it != extensions->end();
95 ++it) {
96 std::string extension_id = (*it)->id();
97 if (!prefs->IsEphemeralApp(extension_id))
98 continue;
100 // Do not remove apps that are running.
101 if (!extensions::util::IsExtensionIdle(extension_id, profile_))
102 continue;
104 DCHECK(registry->GetExtensionById(extension_id,
105 ExtensionRegistry::EVERYTHING));
106 service->UninstallExtension(
107 extension_id,
108 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
109 base::Bind(&base::DoNothing),
110 NULL);
114 void EphemeralAppService::OnExtensionWillBeInstalled(
115 content::BrowserContext* browser_context,
116 const extensions::Extension* extension,
117 bool is_update,
118 bool from_ephemeral,
119 const std::string& old_name) {
120 if (from_ephemeral) {
121 // An ephemeral app was just promoted to a regular installed app.
122 --ephemeral_app_count_;
123 DCHECK_GE(ephemeral_app_count_, 0);
124 HandleEphemeralAppPromoted(extension);
125 } else if (!is_update &&
126 extensions::util::IsEphemeralApp(extension->id(), profile_)) {
127 // A new ephemeral app was launched.
128 ++ephemeral_app_count_;
129 if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
130 TriggerGarbageCollect(
131 base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
136 void EphemeralAppService::OnExtensionUninstalled(
137 content::BrowserContext* browser_context,
138 const extensions::Extension* extension,
139 extensions::UninstallReason reason) {
140 if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
141 --ephemeral_app_count_;
142 DCHECK_GE(ephemeral_app_count_, 0);
146 void EphemeralAppService::OnAppStop(Profile* profile,
147 const std::string& app_id) {
148 if (!extensions::util::IsEphemeralApp(app_id, profile_))
149 return;
151 base::MessageLoop::current()->PostDelayedTask(
152 FROM_HERE,
153 base::Bind(&EphemeralAppService::DisableEphemeralApp,
154 weak_ptr_factory_.GetWeakPtr(),
155 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::MessageLoop::current()->PostDelayedTask(
182 FROM_HERE,
183 base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
184 weak_ptr_factory_.GetWeakPtr()),
185 base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
188 void EphemeralAppService::InitEphemeralAppCount() {
189 scoped_ptr<ExtensionSet> extensions =
190 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
191 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
192 DCHECK(prefs);
194 ephemeral_app_count_ = 0;
195 for (ExtensionSet::const_iterator it = extensions->begin();
196 it != extensions->end(); ++it) {
197 const Extension* extension = it->get();
198 if (prefs->IsEphemeralApp(extension->id()))
199 ++ephemeral_app_count_;
203 void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
204 if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
205 !extensions::util::IsExtensionIdle(app_id, profile_)) {
206 return;
209 // After an ephemeral app has stopped running, unload it from extension
210 // system and disable it to prevent all background activity.
211 ExtensionService* service =
212 ExtensionSystem::Get(profile_)->extension_service();
213 DCHECK(service);
214 service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
217 void EphemeralAppService::DisableEphemeralAppsOnStartup() {
218 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
219 DCHECK(prefs);
220 ExtensionService* service =
221 ExtensionSystem::Get(profile_)->extension_service();
222 DCHECK(service);
224 // Ensure that all inactive ephemeral apps are disabled to prevent all
225 // background activity. This is done on startup to catch any apps that escaped
226 // being disabled on shutdown.
227 scoped_ptr<ExtensionSet> extensions =
228 ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
229 for (ExtensionSet::const_iterator it = extensions->begin();
230 it != extensions->end();
231 ++it) {
232 const Extension* extension = it->get();
233 if (!prefs->IsEphemeralApp(extension->id()))
234 continue;
236 // Only V2 apps are installed ephemerally. Remove other ephemeral app types
237 // that were cached before this policy was introduced.
238 if (!extension->is_platform_app()) {
239 service->UninstallExtension(
240 extension->id(),
241 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
242 base::Bind(&base::DoNothing),
243 NULL);
244 continue;
247 if (!prefs->HasDisableReason(extension->id(),
248 Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
249 !prefs->IsExtensionRunning(extension->id()) &&
250 extensions::util::IsExtensionIdle(extension->id(), profile_)) {
251 service->DisableExtension(extension->id(),
252 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
257 void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
258 // When ephemeral apps are promoted to regular install apps, remove the
259 // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
260 // other reasons.
261 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
262 DCHECK(prefs);
264 int disable_reasons = prefs->GetDisableReasons(app->id());
265 if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
266 prefs->RemoveDisableReason(app->id(),
267 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
268 if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP)
269 prefs->SetExtensionState(app->id(), Extension::ENABLED);
273 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
274 if (!garbage_collect_apps_timer_.IsRunning()) {
275 garbage_collect_apps_timer_.Start(
276 FROM_HERE,
277 delay,
278 this,
279 &EphemeralAppService::GarbageCollectApps);
283 void EphemeralAppService::GarbageCollectApps() {
284 ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
285 DCHECK(registry);
286 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
287 DCHECK(prefs);
289 scoped_ptr<ExtensionSet> extensions =
290 registry->GenerateInstalledExtensionsSet();
292 int app_count = 0;
293 LaunchTimeAppMap app_launch_times;
294 std::set<std::string> remove_app_ids;
296 // Populate a list of ephemeral apps, ordered by their last launch time.
297 for (ExtensionSet::const_iterator it = extensions->begin();
298 it != extensions->end(); ++it) {
299 const Extension* extension = it->get();
300 if (!prefs->IsEphemeralApp(extension->id()))
301 continue;
303 ++app_count;
305 // Do not remove ephemeral apps that are running.
306 if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
307 continue;
309 base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
311 // If the last launch time is invalid, this may be because it was just
312 // installed. So use the install time. If this is also null for some reason,
313 // the app will be removed.
314 if (last_launch_time.is_null())
315 last_launch_time = prefs->GetInstallTime(extension->id());
317 app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
320 ExtensionService* service =
321 ExtensionSystem::Get(profile_)->extension_service();
322 DCHECK(service);
323 // Execute the eviction policies and remove apps marked for deletion.
324 if (!app_launch_times.empty()) {
325 GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
327 for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
328 id != remove_app_ids.end(); ++id) {
329 // Protect against cascading uninstalls.
330 if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING))
331 continue;
333 service->UninstallExtension(
334 *id,
335 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
336 base::Bind(&base::DoNothing),
337 NULL);
342 // static
343 void EphemeralAppService::GetAppsToRemove(
344 int app_count,
345 const LaunchTimeAppMap& app_launch_times,
346 std::set<std::string>* remove_app_ids) {
347 base::Time time_now = base::Time::Now();
348 const base::Time inactive_threshold =
349 time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
350 const base::Time keep_threshold =
351 time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
353 // Visit the apps in order of least recently to most recently launched.
354 for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
355 it != app_launch_times.end(); ++it) {
356 // Cannot remove apps that have been launched recently. So break when we
357 // reach the new apps.
358 if (it->first > keep_threshold)
359 break;
361 // Remove ephemeral apps that have been inactive for a while or if the cache
362 // is larger than the desired size.
363 if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
364 remove_app_ids->insert(it->second);
365 --app_count;
366 } else {
367 break;