• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "chrome/browser/apps/ephemeral_app_service.h"
6 
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/extension_service.h"
11 #include "chrome/browser/extensions/extension_util.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/common/chrome_switches.h"
14 #include "content/public/browser/notification_service.h"
15 #include "content/public/browser/notification_source.h"
16 #include "content/public/browser/notification_types.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/common/extension.h"
22 #include "extensions/common/extension_set.h"
23 
24 using extensions::Extension;
25 using extensions::ExtensionPrefs;
26 using extensions::ExtensionSet;
27 using extensions::ExtensionSystem;
28 
29 namespace {
30 
31 // The number of seconds after startup before performing garbage collection
32 // of ephemeral apps.
33 const int kGarbageCollectAppsStartupDelay = 60;
34 
35 // The number of seconds after an ephemeral app has been installed before
36 // performing garbage collection.
37 const int kGarbageCollectAppsInstallDelay = 15;
38 
39 // When the number of ephemeral apps reaches this count, trigger garbage
40 // collection to trim off the least-recently used apps in excess of
41 // kMaxEphemeralAppsCount.
42 const int kGarbageCollectAppsTriggerCount = 35;
43 
44 }  // namespace
45 
46 const int EphemeralAppService::kAppInactiveThreshold = 10;
47 const int EphemeralAppService::kAppKeepThreshold = 1;
48 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
49 
50 // static
Get(Profile * profile)51 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
52   return EphemeralAppServiceFactory::GetForProfile(profile);
53 }
54 
EphemeralAppService(Profile * profile)55 EphemeralAppService::EphemeralAppService(Profile* profile)
56     : profile_(profile),
57       extension_registry_observer_(this),
58       ephemeral_app_count_(-1) {
59   if (!CommandLine::ForCurrentProcess()->HasSwitch(
60             switches::kEnableEphemeralApps))
61     return;
62 
63   extension_registry_observer_.Add(
64       extensions::ExtensionRegistry::Get(profile_));
65   registrar_.Add(this, chrome::NOTIFICATION_EXTENSIONS_READY,
66                  content::Source<Profile>(profile_));
67   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
68                  content::Source<Profile>(profile_));
69 }
70 
~EphemeralAppService()71 EphemeralAppService::~EphemeralAppService() {
72 }
73 
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)74 void EphemeralAppService::Observe(
75     int type,
76     const content::NotificationSource& source,
77     const content::NotificationDetails& details) {
78   switch (type) {
79     case chrome::NOTIFICATION_EXTENSIONS_READY: {
80       Init();
81       break;
82     }
83     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
84       // Ideally we need to know when the extension system is shutting down.
85       garbage_collect_apps_timer_.Stop();
86       break;
87     }
88     default:
89       NOTREACHED();
90   }
91 }
92 
OnExtensionWillBeInstalled(content::BrowserContext * browser_context,const extensions::Extension * extension,bool is_update,bool from_ephemeral,const std::string & old_name)93 void EphemeralAppService::OnExtensionWillBeInstalled(
94     content::BrowserContext* browser_context,
95     const extensions::Extension* extension,
96     bool is_update,
97     bool from_ephemeral,
98     const std::string& old_name) {
99   if (from_ephemeral) {
100     // An ephemeral app was just promoted to a regular installed app.
101     --ephemeral_app_count_;
102     DCHECK_GE(ephemeral_app_count_, 0);
103   } else if (!is_update &&
104              extensions::util::IsEphemeralApp(extension->id(), profile_)) {
105     ++ephemeral_app_count_;
106     if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
107       TriggerGarbageCollect(
108           base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
109     }
110   }
111 }
112 
OnExtensionUninstalled(content::BrowserContext * browser_context,const extensions::Extension * extension)113 void EphemeralAppService::OnExtensionUninstalled(
114     content::BrowserContext* browser_context,
115     const extensions::Extension* extension) {
116   if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
117     --ephemeral_app_count_;
118     DCHECK_GE(ephemeral_app_count_, 0);
119   }
120 }
121 
Init()122 void EphemeralAppService::Init() {
123   InitEphemeralAppCount();
124   TriggerGarbageCollect(
125       base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
126 }
127 
InitEphemeralAppCount()128 void EphemeralAppService::InitEphemeralAppCount() {
129   scoped_ptr<ExtensionSet> extensions =
130       extensions::ExtensionRegistry::Get(profile_)
131           ->GenerateInstalledExtensionsSet();
132   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
133   DCHECK(prefs);
134 
135   ephemeral_app_count_ = 0;
136   for (ExtensionSet::const_iterator it = extensions->begin();
137        it != extensions->end(); ++it) {
138     const Extension* extension = *it;
139     if (prefs->IsEphemeralApp(extension->id()))
140       ++ephemeral_app_count_;
141   }
142 }
143 
TriggerGarbageCollect(const base::TimeDelta & delay)144 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
145   if (!garbage_collect_apps_timer_.IsRunning()) {
146     garbage_collect_apps_timer_.Start(
147         FROM_HERE,
148         delay,
149         this,
150         &EphemeralAppService::GarbageCollectApps);
151   }
152 }
153 
GarbageCollectApps()154 void EphemeralAppService::GarbageCollectApps() {
155   scoped_ptr<ExtensionSet> extensions =
156       extensions::ExtensionRegistry::Get(profile_)
157           ->GenerateInstalledExtensionsSet();
158   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
159   DCHECK(prefs);
160 
161   int app_count = 0;
162   LaunchTimeAppMap app_launch_times;
163   std::set<std::string> remove_app_ids;
164 
165   // Populate a list of ephemeral apps, ordered by their last launch time.
166   for (ExtensionSet::const_iterator it = extensions->begin();
167        it != extensions->end(); ++it) {
168     const Extension* extension = *it;
169     if (!prefs->IsEphemeralApp(extension->id()))
170       continue;
171 
172     ++app_count;
173 
174     // Do not remove ephemeral apps that are running.
175     if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
176       continue;
177 
178     base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
179 
180     // If the last launch time is invalid, this may be because it was just
181     // installed. So use the install time. If this is also null for some reason,
182     // the app will be removed.
183     if (last_launch_time.is_null())
184       last_launch_time = prefs->GetInstallTime(extension->id());
185 
186     app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
187   }
188 
189   ExtensionService* service =
190       ExtensionSystem::Get(profile_)->extension_service();
191   DCHECK(service);
192   // Execute the replacement policies and remove apps marked for deletion.
193   if (!app_launch_times.empty()) {
194     GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
195     for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
196          id != remove_app_ids.end(); ++id) {
197       if (service->UninstallExtension(*id, false, NULL))
198         --app_count;
199     }
200   }
201 
202   ephemeral_app_count_ = app_count;
203 }
204 
205 // static
GetAppsToRemove(int app_count,const LaunchTimeAppMap & app_launch_times,std::set<std::string> * remove_app_ids)206 void EphemeralAppService::GetAppsToRemove(
207     int app_count,
208     const LaunchTimeAppMap& app_launch_times,
209     std::set<std::string>* remove_app_ids) {
210   base::Time time_now = base::Time::Now();
211   const base::Time inactive_threshold =
212       time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
213   const base::Time keep_threshold =
214       time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
215 
216   // Visit the apps in order of least recently to most recently launched.
217   for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
218        it != app_launch_times.end(); ++it) {
219     // Cannot remove apps that have been launched recently. So break when we
220     // reach the new apps.
221     if (it->first > keep_threshold)
222         break;
223 
224     // Remove ephemeral apps that have been inactive for a while or if the cache
225     // is larger than the desired size.
226     if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
227       remove_app_ids->insert(it->second);
228       --app_count;
229     } else {
230       break;
231     }
232   }
233 }
234