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 "base/command_line.h"
8 #include "chrome/browser/apps/ephemeral_app_service_factory.h"
9 #include "chrome/browser/chrome_notification_types.h"
10 #include "chrome/browser/extensions/data_deleter.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 "content/public/browser/notification_service.h"
16 #include "content/public/browser/notification_source.h"
17 #include "content/public/browser/notification_types.h"
18 #include "extensions/browser/extension_prefs.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/browser/extension_system.h"
21 #include "extensions/browser/extension_util.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_set.h"
25 using extensions::Extension
;
26 using extensions::ExtensionInfo
;
27 using extensions::ExtensionPrefs
;
28 using extensions::ExtensionSet
;
29 using extensions::ExtensionSystem
;
30 using extensions::InstalledExtensionInfo
;
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 startup before performing garbage collection
48 // of the data of evicted ephemeral apps.
49 const int kGarbageCollectDataStartupDelay
= 120;
53 const int EphemeralAppService::kAppInactiveThreshold
= 10;
54 const int EphemeralAppService::kAppKeepThreshold
= 1;
55 const int EphemeralAppService::kMaxEphemeralAppsCount
= 30;
56 const int EphemeralAppService::kDataInactiveThreshold
= 90;
59 EphemeralAppService
* EphemeralAppService::Get(Profile
* profile
) {
60 return EphemeralAppServiceFactory::GetForProfile(profile
);
63 EphemeralAppService::EphemeralAppService(Profile
* profile
)
65 extension_registry_observer_(this),
66 ephemeral_app_count_(-1) {
67 if (!CommandLine::ForCurrentProcess()->HasSwitch(
68 switches::kEnableEphemeralApps
))
71 extension_registry_observer_
.Add(
72 extensions::ExtensionRegistry::Get(profile_
));
73 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY
,
74 content::Source
<Profile
>(profile_
));
75 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED
,
76 content::Source
<Profile
>(profile_
));
79 EphemeralAppService::~EphemeralAppService() {
82 void EphemeralAppService::Observe(
84 const content::NotificationSource
& source
,
85 const content::NotificationDetails
& details
) {
87 case chrome::NOTIFICATION_EXTENSIONS_READY
: {
91 case chrome::NOTIFICATION_PROFILE_DESTROYED
: {
92 // Ideally we need to know when the extension system is shutting down.
93 garbage_collect_apps_timer_
.Stop();
101 void EphemeralAppService::OnExtensionWillBeInstalled(
102 content::BrowserContext
* browser_context
,
103 const extensions::Extension
* extension
,
106 const std::string
& old_name
) {
107 if (from_ephemeral
) {
108 // An ephemeral app was just promoted to a regular installed app.
109 --ephemeral_app_count_
;
110 DCHECK_GE(ephemeral_app_count_
, 0);
111 } else if (!is_update
&&
112 extensions::util::IsEphemeralApp(extension
->id(), profile_
)) {
113 ++ephemeral_app_count_
;
114 if (ephemeral_app_count_
>= kGarbageCollectAppsTriggerCount
) {
115 TriggerGarbageCollect(
116 base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay
));
121 void EphemeralAppService::OnExtensionUninstalled(
122 content::BrowserContext
* browser_context
,
123 const extensions::Extension
* extension
) {
124 if (extensions::util::IsEphemeralApp(extension
->id(), profile_
)) {
125 --ephemeral_app_count_
;
126 DCHECK_GE(ephemeral_app_count_
, 0);
130 void EphemeralAppService::Init() {
131 InitEphemeralAppCount();
132 TriggerGarbageCollect(
133 base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay
));
135 garbage_collect_data_timer_
.Start(
137 base::TimeDelta::FromSeconds(kGarbageCollectDataStartupDelay
),
139 &EphemeralAppService::GarbageCollectData
);
142 void EphemeralAppService::InitEphemeralAppCount() {
143 scoped_ptr
<ExtensionSet
> extensions
=
144 extensions::ExtensionRegistry::Get(profile_
)
145 ->GenerateInstalledExtensionsSet();
146 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
149 ephemeral_app_count_
= 0;
150 for (ExtensionSet::const_iterator it
= extensions
->begin();
151 it
!= extensions
->end(); ++it
) {
152 const Extension
* extension
= *it
;
153 if (prefs
->IsEphemeralApp(extension
->id()))
154 ++ephemeral_app_count_
;
158 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta
& delay
) {
159 if (!garbage_collect_apps_timer_
.IsRunning()) {
160 garbage_collect_apps_timer_
.Start(
164 &EphemeralAppService::GarbageCollectApps
);
168 void EphemeralAppService::GarbageCollectApps() {
169 scoped_ptr
<ExtensionSet
> extensions
=
170 extensions::ExtensionRegistry::Get(profile_
)
171 ->GenerateInstalledExtensionsSet();
172 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
176 LaunchTimeAppMap app_launch_times
;
177 std::set
<std::string
> remove_app_ids
;
179 // Populate a list of ephemeral apps, ordered by their last launch time.
180 for (ExtensionSet::const_iterator it
= extensions
->begin();
181 it
!= extensions
->end(); ++it
) {
182 const Extension
* extension
= *it
;
183 if (!prefs
->IsEphemeralApp(extension
->id()))
188 // Do not remove ephemeral apps that are running.
189 if (!extensions::util::IsExtensionIdle(extension
->id(), profile_
))
192 base::Time last_launch_time
= prefs
->GetLastLaunchTime(extension
->id());
194 // If the last launch time is invalid, this may be because it was just
195 // installed. So use the install time. If this is also null for some reason,
196 // the app will be removed.
197 if (last_launch_time
.is_null())
198 last_launch_time
= prefs
->GetInstallTime(extension
->id());
200 app_launch_times
.insert(std::make_pair(last_launch_time
, extension
->id()));
203 ExtensionService
* service
=
204 ExtensionSystem::Get(profile_
)->extension_service();
206 // Execute the replacement policies and remove apps marked for deletion.
207 if (!app_launch_times
.empty()) {
208 GetAppsToRemove(app_count
, app_launch_times
, &remove_app_ids
);
209 for (std::set
<std::string
>::const_iterator id
= remove_app_ids
.begin();
210 id
!= remove_app_ids
.end(); ++id
) {
211 if (service
->UninstallExtension(*id
, false, NULL
))
216 ephemeral_app_count_
= app_count
;
220 void EphemeralAppService::GetAppsToRemove(
222 const LaunchTimeAppMap
& app_launch_times
,
223 std::set
<std::string
>* remove_app_ids
) {
224 base::Time time_now
= base::Time::Now();
225 const base::Time inactive_threshold
=
226 time_now
- base::TimeDelta::FromDays(kAppInactiveThreshold
);
227 const base::Time keep_threshold
=
228 time_now
- base::TimeDelta::FromDays(kAppKeepThreshold
);
230 // Visit the apps in order of least recently to most recently launched.
231 for (LaunchTimeAppMap::const_iterator it
= app_launch_times
.begin();
232 it
!= app_launch_times
.end(); ++it
) {
233 // Cannot remove apps that have been launched recently. So break when we
234 // reach the new apps.
235 if (it
->first
> keep_threshold
)
238 // Remove ephemeral apps that have been inactive for a while or if the cache
239 // is larger than the desired size.
240 if (it
->first
< inactive_threshold
|| app_count
> kMaxEphemeralAppsCount
) {
241 remove_app_ids
->insert(it
->second
);
249 void EphemeralAppService::GarbageCollectData() {
250 ExtensionService
* service
=
251 ExtensionSystem::Get(profile_
)->extension_service();
253 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile_
);
255 scoped_ptr
<ExtensionPrefs::ExtensionsInfo
> evicted_apps_info(
256 prefs
->GetEvictedEphemeralAppsInfo());
258 base::Time time_now
= base::Time::Now();
259 const base::Time inactive_threshold
=
260 time_now
- base::TimeDelta::FromDays(kDataInactiveThreshold
);
262 for (size_t i
= 0; i
< evicted_apps_info
->size(); ++i
) {
263 ExtensionInfo
* info
= evicted_apps_info
->at(i
).get();
264 base::Time last_launch_time
= prefs
->GetLastLaunchTime(info
->extension_id
);
265 if (last_launch_time
> inactive_threshold
)
268 // Sanity check to ensure the app is not currently installed.
269 if (service
->GetInstalledExtension(info
->extension_id
)) {
274 // Ensure the app is not waiting to be installed.
275 scoped_ptr
<ExtensionInfo
> delayed_install(
276 prefs
->GetDelayedInstallInfo(info
->extension_id
));
277 if (delayed_install
.get())
280 if (info
->extension_manifest
.get()) {
282 scoped_refptr
<const Extension
> extension(Extension::Create(
283 info
->extension_path
,
284 info
->extension_location
,
285 *info
->extension_manifest
,
286 prefs
->GetCreationFlags(info
->extension_id
),
291 extensions::DataDeleter::StartDeleting(profile_
, extension
.get());
294 prefs
->RemoveEvictedEphemeralApp(info
->extension_id
);