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
;
34 // The number of seconds after startup before performing garbage collection
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
49 const int kDefaultDisableAppDelay
= 1;
51 // The number of seconds after startup before disabling inactive ephemeral apps.
52 const int kDisableAppsOnStartupDelay
= 5;
56 const int EphemeralAppService::kAppInactiveThreshold
= 10;
57 const int EphemeralAppService::kAppKeepThreshold
= 1;
58 const int EphemeralAppService::kMaxEphemeralAppsCount
= 30;
61 EphemeralAppService
* EphemeralAppService::Get(Profile
* profile
) {
62 return EphemeralAppServiceFactory::GetForProfile(profile
);
65 EphemeralAppService::EphemeralAppService(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(
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_
);
86 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
88 ExtensionService
* service
=
89 ExtensionSystem::Get(profile_
)->extension_service();
92 scoped_ptr
<ExtensionSet
> extensions
=
93 registry
->GenerateInstalledExtensionsSet();
95 for (ExtensionSet::const_iterator it
= extensions
->begin();
96 it
!= extensions
->end();
98 std::string extension_id
= (*it
)->id();
99 if (!prefs
->IsEphemeralApp(extension_id
))
102 // Do not remove apps that are running.
103 if (!extensions::util::IsExtensionIdle(extension_id
, profile_
))
106 DCHECK(registry
->GetExtensionById(extension_id
,
107 ExtensionRegistry::EVERYTHING
));
108 service
->UninstallExtension(
110 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION
,
111 base::Bind(&base::DoNothing
),
116 void EphemeralAppService::OnExtensionWillBeInstalled(
117 content::BrowserContext
* browser_context
,
118 const extensions::Extension
* extension
,
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_
))
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();
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
))
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_
);
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_
)) {
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();
213 service
->DisableExtension(app_id
, Extension::DISABLE_INACTIVE_EPHEMERAL_APP
);
216 void EphemeralAppService::DisableEphemeralAppsOnStartup() {
217 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
219 ExtensionService
* service
=
220 ExtensionSystem::Get(profile_
)->extension_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();
231 const Extension
* extension
= it
->get();
232 if (!prefs
->IsEphemeralApp(extension
->id()))
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(
240 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION
,
241 base::Bind(&base::DoNothing
),
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
260 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
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());
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(
281 &EphemeralAppService::GarbageCollectApps
);
285 void EphemeralAppService::GarbageCollectApps() {
286 ExtensionRegistry
* registry
= ExtensionRegistry::Get(profile_
);
288 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
291 scoped_ptr
<ExtensionSet
> extensions
=
292 registry
->GenerateInstalledExtensionsSet();
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()))
307 // Do not remove ephemeral apps that are running.
308 if (!extensions::util::IsExtensionIdle(extension
->id(), profile_
))
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();
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
))
335 service
->UninstallExtension(
337 extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION
,
338 base::Bind(&base::DoNothing
),
345 void EphemeralAppService::GetAppsToRemove(
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
)
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
);