Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / apps / ephemeral_app_service.cc
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 "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/chrome_notification_types.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/extension_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/chrome_switches.h"
16 #include "content/public/browser/notification_service.h"
17 #include "content/public/browser/notification_source.h"
18 #include "content/public/browser/notification_types.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/extension_util.h"
23 #include "extensions/browser/notification_types.h"
24 #include "extensions/browser/uninstall_reason.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_set.h"
27
28 using extensions::Extension;
29 using extensions::ExtensionPrefs;
30 using extensions::ExtensionRegistry;
31 using extensions::ExtensionSet;
32 using extensions::ExtensionSystem;
33
34 namespace {
35
36 // The number of seconds after startup before performing garbage collection
37 // of ephemeral apps.
38 const int kGarbageCollectAppsStartupDelay = 60;
39
40 // The number of seconds after an ephemeral app has been installed before
41 // performing garbage collection.
42 const int kGarbageCollectAppsInstallDelay = 15;
43
44 // When the number of ephemeral apps reaches this count, trigger garbage
45 // collection to trim off the least-recently used apps in excess of
46 // kMaxEphemeralAppsCount.
47 const int kGarbageCollectAppsTriggerCount = 35;
48
49 // The number of seconds after an app has stopped running before it will be
50 // disabled.
51 const int kDefaultDisableAppDelay = 1;
52
53 // The number of seconds after startup before disabling inactive ephemeral apps.
54 const int kDisableAppsOnStartupDelay = 5;
55
56 }  // namespace
57
58 const int EphemeralAppService::kAppInactiveThreshold = 10;
59 const int EphemeralAppService::kAppKeepThreshold = 1;
60 const int EphemeralAppService::kMaxEphemeralAppsCount = 30;
61
62 // static
63 EphemeralAppService* EphemeralAppService::Get(Profile* profile) {
64   return EphemeralAppServiceFactory::GetForProfile(profile);
65 }
66
67 EphemeralAppService::EphemeralAppService(Profile* profile)
68     : profile_(profile),
69       extension_registry_observer_(this),
70       app_lifetime_monitor_observer_(this),
71       ephemeral_app_count_(-1),
72       disable_idle_app_delay_(kDefaultDisableAppDelay),
73       weak_ptr_factory_(this) {
74   registrar_.Add(this,
75                  extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED,
76                  content::Source<Profile>(profile_));
77 }
78
79 EphemeralAppService::~EphemeralAppService() {
80 }
81
82 void EphemeralAppService::ClearCachedApps() {
83   // Cancel any pending garbage collects.
84   garbage_collect_apps_timer_.Stop();
85
86   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
87   DCHECK(registry);
88   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
89   DCHECK(prefs);
90   ExtensionService* service =
91       ExtensionSystem::Get(profile_)->extension_service();
92   DCHECK(service);
93
94   scoped_ptr<ExtensionSet> extensions =
95       registry->GenerateInstalledExtensionsSet();
96
97   for (ExtensionSet::const_iterator it = extensions->begin();
98        it != extensions->end();
99        ++it) {
100     std::string extension_id = (*it)->id();
101     if (!prefs->IsEphemeralApp(extension_id))
102       continue;
103
104     // Do not remove apps that are running.
105     if (!extensions::util::IsExtensionIdle(extension_id, profile_))
106       continue;
107
108     DCHECK(registry->GetExtensionById(extension_id,
109                                       ExtensionRegistry::EVERYTHING));
110     service->UninstallExtension(
111         extension_id,
112         extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
113         base::Bind(&base::DoNothing),
114         NULL);
115   }
116 }
117
118 void EphemeralAppService::Observe(
119     int type,
120     const content::NotificationSource& source,
121     const content::NotificationDetails& details) {
122   switch (type) {
123     case extensions::NOTIFICATION_EXTENSIONS_READY_DEPRECATED: {
124       Init();
125       break;
126     }
127     default:
128       NOTREACHED();
129   }
130 }
131
132 void EphemeralAppService::OnExtensionWillBeInstalled(
133     content::BrowserContext* browser_context,
134     const extensions::Extension* extension,
135     bool is_update,
136     bool from_ephemeral,
137     const std::string& old_name) {
138   if (from_ephemeral) {
139     // An ephemeral app was just promoted to a regular installed app.
140     --ephemeral_app_count_;
141     DCHECK_GE(ephemeral_app_count_, 0);
142     HandleEphemeralAppPromoted(extension);
143   } else if (!is_update &&
144              extensions::util::IsEphemeralApp(extension->id(), profile_)) {
145     // A new ephemeral app was launched.
146     ++ephemeral_app_count_;
147     if (ephemeral_app_count_ >= kGarbageCollectAppsTriggerCount) {
148       TriggerGarbageCollect(
149           base::TimeDelta::FromSeconds(kGarbageCollectAppsInstallDelay));
150     }
151   }
152 }
153
154 void EphemeralAppService::OnExtensionUninstalled(
155     content::BrowserContext* browser_context,
156     const extensions::Extension* extension,
157     extensions::UninstallReason reason) {
158   if (extensions::util::IsEphemeralApp(extension->id(), profile_)) {
159     --ephemeral_app_count_;
160     DCHECK_GE(ephemeral_app_count_, 0);
161   }
162 }
163
164 void EphemeralAppService::OnAppStop(Profile* profile,
165                                     const std::string& app_id) {
166   if (!extensions::util::IsEphemeralApp(app_id, profile_))
167     return;
168
169   base::MessageLoop::current()->PostDelayedTask(
170       FROM_HERE,
171       base::Bind(&EphemeralAppService::DisableEphemeralApp,
172                  weak_ptr_factory_.GetWeakPtr(),
173                  app_id),
174       base::TimeDelta::FromSeconds(disable_idle_app_delay_));
175 }
176
177 void EphemeralAppService::OnChromeTerminating() {
178   garbage_collect_apps_timer_.Stop();
179
180   extension_registry_observer_.RemoveAll();
181   app_lifetime_monitor_observer_.RemoveAll();
182 }
183
184 void EphemeralAppService::Init() {
185   InitEphemeralAppCount();
186
187   // Start observing.
188   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
189   app_lifetime_monitor_observer_.Add(
190       apps::AppLifetimeMonitorFactory::GetForProfile(profile_));
191
192   // Execute startup clean up tasks (except during tests).
193   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType))
194     return;
195
196   TriggerGarbageCollect(
197       base::TimeDelta::FromSeconds(kGarbageCollectAppsStartupDelay));
198
199   base::MessageLoop::current()->PostDelayedTask(
200       FROM_HERE,
201       base::Bind(&EphemeralAppService::DisableEphemeralAppsOnStartup,
202                  weak_ptr_factory_.GetWeakPtr()),
203       base::TimeDelta::FromSeconds(kDisableAppsOnStartupDelay));
204 }
205
206 void EphemeralAppService::InitEphemeralAppCount() {
207   scoped_ptr<ExtensionSet> extensions =
208       ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
209   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
210   DCHECK(prefs);
211
212   ephemeral_app_count_ = 0;
213   for (ExtensionSet::const_iterator it = extensions->begin();
214        it != extensions->end(); ++it) {
215     const Extension* extension = it->get();
216     if (prefs->IsEphemeralApp(extension->id()))
217       ++ephemeral_app_count_;
218   }
219 }
220
221 void EphemeralAppService::DisableEphemeralApp(const std::string& app_id) {
222   if (!extensions::util::IsEphemeralApp(app_id, profile_) ||
223       !extensions::util::IsExtensionIdle(app_id, profile_)) {
224     return;
225   }
226
227   // After an ephemeral app has stopped running, unload it from extension
228   // system and disable it to prevent all background activity.
229   ExtensionService* service =
230       ExtensionSystem::Get(profile_)->extension_service();
231   DCHECK(service);
232   service->DisableExtension(app_id, Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
233 }
234
235 void EphemeralAppService::DisableEphemeralAppsOnStartup() {
236   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
237   DCHECK(prefs);
238   ExtensionService* service =
239       ExtensionSystem::Get(profile_)->extension_service();
240   DCHECK(service);
241
242   // Ensure that all inactive ephemeral apps are disabled to prevent all
243   // background activity. This is done on startup to catch any apps that escaped
244   // being disabled on shutdown.
245   scoped_ptr<ExtensionSet> extensions =
246       ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet();
247   for (ExtensionSet::const_iterator it = extensions->begin();
248        it != extensions->end();
249        ++it) {
250     const Extension* extension = it->get();
251     if (!prefs->IsEphemeralApp(extension->id()))
252       continue;
253
254     // Only V2 apps are installed ephemerally. Remove other ephemeral app types
255     // that were cached before this policy was introduced.
256     if (!extension->is_platform_app()) {
257       service->UninstallExtension(
258           extension->id(),
259           extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
260           base::Bind(&base::DoNothing),
261           NULL);
262       continue;
263     }
264
265     if (!prefs->HasDisableReason(extension->id(),
266                                  Extension::DISABLE_INACTIVE_EPHEMERAL_APP) &&
267         !prefs->IsExtensionRunning(extension->id()) &&
268         extensions::util::IsExtensionIdle(extension->id(), profile_)) {
269       service->DisableExtension(extension->id(),
270                                 Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
271     }
272   }
273 }
274
275 void EphemeralAppService::HandleEphemeralAppPromoted(const Extension* app) {
276   // When ephemeral apps are promoted to regular install apps, remove the
277   // DISABLE_INACTIVE_EPHEMERAL_APP reason and enable the app if there are no
278   // other reasons.
279   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
280   DCHECK(prefs);
281
282   int disable_reasons = prefs->GetDisableReasons(app->id());
283   if (disable_reasons & Extension::DISABLE_INACTIVE_EPHEMERAL_APP) {
284     prefs->RemoveDisableReason(app->id(),
285                                Extension::DISABLE_INACTIVE_EPHEMERAL_APP);
286     if (disable_reasons == Extension::DISABLE_INACTIVE_EPHEMERAL_APP)
287       prefs->SetExtensionState(app->id(), Extension::ENABLED);
288   }
289 }
290
291 void EphemeralAppService::TriggerGarbageCollect(const base::TimeDelta& delay) {
292   if (!garbage_collect_apps_timer_.IsRunning()) {
293     garbage_collect_apps_timer_.Start(
294         FROM_HERE,
295         delay,
296         this,
297         &EphemeralAppService::GarbageCollectApps);
298   }
299 }
300
301 void EphemeralAppService::GarbageCollectApps() {
302   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
303   DCHECK(registry);
304   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
305   DCHECK(prefs);
306
307   scoped_ptr<ExtensionSet> extensions =
308       registry->GenerateInstalledExtensionsSet();
309
310   int app_count = 0;
311   LaunchTimeAppMap app_launch_times;
312   std::set<std::string> remove_app_ids;
313
314   // Populate a list of ephemeral apps, ordered by their last launch time.
315   for (ExtensionSet::const_iterator it = extensions->begin();
316        it != extensions->end(); ++it) {
317     const Extension* extension = it->get();
318     if (!prefs->IsEphemeralApp(extension->id()))
319       continue;
320
321     ++app_count;
322
323     // Do not remove ephemeral apps that are running.
324     if (!extensions::util::IsExtensionIdle(extension->id(), profile_))
325       continue;
326
327     base::Time last_launch_time = prefs->GetLastLaunchTime(extension->id());
328
329     // If the last launch time is invalid, this may be because it was just
330     // installed. So use the install time. If this is also null for some reason,
331     // the app will be removed.
332     if (last_launch_time.is_null())
333       last_launch_time = prefs->GetInstallTime(extension->id());
334
335     app_launch_times.insert(std::make_pair(last_launch_time, extension->id()));
336   }
337
338   ExtensionService* service =
339       ExtensionSystem::Get(profile_)->extension_service();
340   DCHECK(service);
341   // Execute the eviction policies and remove apps marked for deletion.
342   if (!app_launch_times.empty()) {
343     GetAppsToRemove(app_count, app_launch_times, &remove_app_ids);
344
345     for (std::set<std::string>::const_iterator id = remove_app_ids.begin();
346          id != remove_app_ids.end(); ++id) {
347       // Protect against cascading uninstalls.
348       if (!registry->GetExtensionById(*id, ExtensionRegistry::EVERYTHING))
349         continue;
350
351       service->UninstallExtension(
352           *id,
353           extensions::UNINSTALL_REASON_ORPHANED_EPHEMERAL_EXTENSION,
354           base::Bind(&base::DoNothing),
355           NULL);
356     }
357   }
358 }
359
360 // static
361 void EphemeralAppService::GetAppsToRemove(
362     int app_count,
363     const LaunchTimeAppMap& app_launch_times,
364     std::set<std::string>* remove_app_ids) {
365   base::Time time_now = base::Time::Now();
366   const base::Time inactive_threshold =
367       time_now - base::TimeDelta::FromDays(kAppInactiveThreshold);
368   const base::Time keep_threshold =
369       time_now - base::TimeDelta::FromDays(kAppKeepThreshold);
370
371   // Visit the apps in order of least recently to most recently launched.
372   for (LaunchTimeAppMap::const_iterator it = app_launch_times.begin();
373        it != app_launch_times.end(); ++it) {
374     // Cannot remove apps that have been launched recently. So break when we
375     // reach the new apps.
376     if (it->first > keep_threshold)
377         break;
378
379     // Remove ephemeral apps that have been inactive for a while or if the cache
380     // is larger than the desired size.
381     if (it->first < inactive_threshold || app_count > kMaxEphemeralAppsCount) {
382       remove_app_ids->insert(it->second);
383       --app_count;
384     } else {
385       break;
386     }
387   }
388 }