kiosk_app_manager.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/chromeos/app_mode/kiosk_app_manager.h"
6
7#include <map>
8#include <set>
9
10#include "base/bind.h"
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/path_service.h"
14#include "base/prefs/pref_registry_simple.h"
15#include "base/stl_util.h"
16#include "base/values.h"
17#include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
18#include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
19#include "chrome/browser/chromeos/login/user_manager.h"
20#include "chrome/browser/chromeos/settings/cros_settings.h"
21#include "chrome/common/chrome_notification_types.h"
22#include "chrome/common/chrome_paths.h"
23#include "chromeos/cryptohome/async_method_caller.h"
24
25namespace chromeos {
26
27namespace {
28
29std::string FormatKioskAppUserId(const std::string& app_id) {
30  return app_id + '@' + UserManager::kKioskAppUserDomain;
31}
32
33void OnRemoveAppCryptohomeComplete(const std::string& app,
34                                   bool success,
35                                   cryptohome::MountError return_code) {
36  if (!success) {
37    LOG(ERROR) << "Remove cryptohome for " << app
38        << " failed, return code: " << return_code;
39  }
40}
41
42// Decodes a device-local account dictionary and extracts the |account_id| and
43// |app_id| if decoding is successful and the entry refers to a Kiosk App.
44bool DecodeDeviceLocalAccount(const base::Value* account_spec,
45                              std::string* account_id,
46                              std::string* app_id) {
47  const base::DictionaryValue* account_dict = NULL;
48  if (!account_spec->GetAsDictionary(&account_dict)) {
49    NOTREACHED();
50    return false;
51  }
52
53  if (!account_dict->GetStringWithoutPathExpansion(
54          kAccountsPrefDeviceLocalAccountsKeyId, account_id)) {
55    LOG(ERROR) << "Account ID missing";
56    return false;
57  }
58
59  int type;
60  if (!account_dict->GetIntegerWithoutPathExpansion(
61          kAccountsPrefDeviceLocalAccountsKeyType, &type) ||
62      type != DEVICE_LOCAL_ACCOUNT_TYPE_KIOSK_APP) {
63    // Not a kiosk app.
64    return false;
65  }
66
67  if (!account_dict->GetStringWithoutPathExpansion(
68          kAccountsPrefDeviceLocalAccountsKeyKioskAppId, app_id)) {
69    LOG(ERROR) << "Kiosk app id missing for " << *account_id;
70    return false;
71  }
72
73  return true;
74}
75
76}  // namespace
77
78// static
79const char KioskAppManager::kKioskDictionaryName[] = "kiosk";
80const char KioskAppManager::kKeyApps[] = "apps";
81const char KioskAppManager::kIconCacheDir[] = "kiosk";
82
83// static
84static base::LazyInstance<KioskAppManager> instance = LAZY_INSTANCE_INITIALIZER;
85KioskAppManager* KioskAppManager::Get() {
86  return instance.Pointer();
87}
88
89// static
90void KioskAppManager::Shutdown() {
91  if (instance == NULL)
92    return;
93
94  instance.Pointer()->CleanUp();
95}
96
97// static
98void KioskAppManager::RegisterPrefs(PrefRegistrySimple* registry) {
99  registry->RegisterDictionaryPref(kKioskDictionaryName);
100}
101
102KioskAppManager::App::App(const KioskAppData& data)
103    : id(data.id()),
104      name(data.name()),
105      icon(data.icon()),
106      is_loading(data.IsLoading()) {
107}
108
109KioskAppManager::App::App() : is_loading(false) {}
110KioskAppManager::App::~App() {}
111
112std::string KioskAppManager::GetAutoLaunchApp() const {
113  return auto_launch_app_id_;
114}
115
116void KioskAppManager::SetAutoLaunchApp(const std::string& app_id) {
117  CrosSettings::Get()->SetString(
118      kAccountsPrefDeviceLocalAccountAutoLoginId,
119      app_id.empty() ? std::string() : FormatKioskAppUserId(app_id));
120  CrosSettings::Get()->SetInteger(
121      kAccountsPrefDeviceLocalAccountAutoLoginDelay, 0);
122}
123
124void KioskAppManager::AddApp(const std::string& app_id) {
125  CrosSettings* cros_settings = CrosSettings::Get();
126  const base::ListValue* accounts_list = NULL;
127  cros_settings->GetList(kAccountsPrefDeviceLocalAccounts, &accounts_list);
128
129  // Don't insert if the app if it's already in the list.
130  base::ListValue new_accounts_list;
131  if (accounts_list) {
132    for (base::ListValue::const_iterator entry(accounts_list->begin());
133         entry != accounts_list->end(); ++entry) {
134      std::string account_id;
135      std::string kiosk_app_id;
136      if (DecodeDeviceLocalAccount(*entry, &account_id, &kiosk_app_id) &&
137          kiosk_app_id == app_id) {
138        return;
139      }
140      new_accounts_list.Append((*entry)->DeepCopy());
141    }
142  }
143
144  // Add the new account.
145  scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue());
146  new_entry->SetStringWithoutPathExpansion(
147      kAccountsPrefDeviceLocalAccountsKeyId, FormatKioskAppUserId(app_id));
148  new_entry->SetIntegerWithoutPathExpansion(
149      kAccountsPrefDeviceLocalAccountsKeyType,
150      DEVICE_LOCAL_ACCOUNT_TYPE_KIOSK_APP);
151  new_entry->SetStringWithoutPathExpansion(
152      kAccountsPrefDeviceLocalAccountsKeyKioskAppId, app_id);
153  new_accounts_list.Append(new_entry.release());
154  cros_settings->Set(kAccountsPrefDeviceLocalAccounts, new_accounts_list);
155}
156
157void KioskAppManager::RemoveApp(const std::string& app_id) {
158  CrosSettings* cros_settings = CrosSettings::Get();
159  const base::ListValue* accounts_list = NULL;
160  cros_settings->GetList(kAccountsPrefDeviceLocalAccounts, &accounts_list);
161  if (!accounts_list)
162    return;
163
164  // Duplicate the list, filtering out entries that match |app_id|.
165  base::ListValue new_accounts_list;
166  for (base::ListValue::const_iterator entry(accounts_list->begin());
167       entry != accounts_list->end(); ++entry) {
168    std::string account_id;
169    std::string kiosk_app_id;
170    if (DecodeDeviceLocalAccount(*entry, &account_id, &kiosk_app_id) &&
171        kiosk_app_id == app_id) {
172      continue;
173    }
174    new_accounts_list.Append((*entry)->DeepCopy());
175  }
176
177  cros_settings->Set(kAccountsPrefDeviceLocalAccounts, new_accounts_list);
178}
179
180void KioskAppManager::GetApps(Apps* apps) const {
181  apps->reserve(apps_.size());
182  for (size_t i = 0; i < apps_.size(); ++i)
183    apps->push_back(App(*apps_[i]));
184}
185
186bool KioskAppManager::GetApp(const std::string& app_id, App* app) const {
187  const KioskAppData* data = GetAppData(app_id);
188  if (!data)
189    return false;
190
191  *app = App(*data);
192  return true;
193}
194
195const base::RefCountedString* KioskAppManager::GetAppRawIcon(
196    const std::string& app_id) const {
197  const KioskAppData* data = GetAppData(app_id);
198  if (!data)
199    return NULL;
200
201  return data->raw_icon();
202}
203
204bool KioskAppManager::GetDisableBailoutShortcut() const {
205  bool enable;
206  if (CrosSettings::Get()->GetBoolean(
207          kAccountsPrefDeviceLocalAccountAutoLoginBailoutEnabled, &enable)) {
208    return !enable;
209  }
210
211  return false;
212}
213
214void KioskAppManager::AddObserver(KioskAppManagerObserver* observer) {
215  observers_.AddObserver(observer);
216}
217
218void KioskAppManager::RemoveObserver(KioskAppManagerObserver* observer) {
219  observers_.RemoveObserver(observer);
220}
221
222KioskAppManager::KioskAppManager() {
223  UpdateAppData();
224  CrosSettings::Get()->AddSettingsObserver(
225      kAccountsPrefDeviceLocalAccounts, this);
226  CrosSettings::Get()->AddSettingsObserver(
227      kAccountsPrefDeviceLocalAccountAutoLoginId, this);
228}
229
230KioskAppManager::~KioskAppManager() {}
231
232void KioskAppManager::CleanUp() {
233  CrosSettings::Get()->RemoveSettingsObserver(
234      kAccountsPrefDeviceLocalAccounts, this);
235  CrosSettings::Get()->RemoveSettingsObserver(
236      kAccountsPrefDeviceLocalAccountAutoLoginId, this);
237  apps_.clear();
238}
239
240const KioskAppData* KioskAppManager::GetAppData(
241    const std::string& app_id) const {
242  for (size_t i = 0; i < apps_.size(); ++i) {
243    const KioskAppData* data = apps_[i];
244    if (data->id() == app_id)
245      return data;
246  }
247
248  return NULL;
249}
250
251void KioskAppManager::UpdateAppData() {
252  // Gets app id to data mapping for existing apps.
253  std::map<std::string, KioskAppData*> old_apps;
254  for (size_t i = 0; i < apps_.size(); ++i)
255    old_apps[apps_[i]->id()] = apps_[i];
256  apps_.weak_clear();  // |old_apps| takes ownership
257
258  auto_launch_app_id_.clear();
259  std::string auto_login_account_id;
260  CrosSettings::Get()->GetString(kAccountsPrefDeviceLocalAccountAutoLoginId,
261                                 &auto_login_account_id);
262
263  const base::ListValue* local_accounts;
264  if (CrosSettings::Get()->GetList(kAccountsPrefDeviceLocalAccounts,
265                                   &local_accounts)) {
266    // Re-populates |apps_| and reuses existing KioskAppData when possible.
267    for (base::ListValue::const_iterator account(local_accounts->begin());
268         account != local_accounts->end(); ++account) {
269      std::string account_id;
270      std::string kiosk_app_id;
271      if (!DecodeDeviceLocalAccount(*account, &account_id, &kiosk_app_id))
272        continue;
273
274      if (account_id == auto_login_account_id)
275        auto_launch_app_id_ = kiosk_app_id;
276
277      // TODO(mnissler): Support non-CWS update URLs.
278
279      std::map<std::string, KioskAppData*>::iterator old_it =
280          old_apps.find(kiosk_app_id);
281      if (old_it != old_apps.end()) {
282        apps_.push_back(old_it->second);
283        old_apps.erase(old_it);
284      } else {
285        KioskAppData* new_app = new KioskAppData(this, kiosk_app_id);
286        apps_.push_back(new_app);  // Takes ownership of |new_app|.
287        new_app->Load();
288      }
289    }
290  }
291
292  // Clears cache and deletes the remaining old data.
293  for (std::map<std::string, KioskAppData*>::iterator it = old_apps.begin();
294       it != old_apps.end(); ++it) {
295    it->second->ClearCache();
296    cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove(
297        it->first, base::Bind(&OnRemoveAppCryptohomeComplete, it->first));
298  }
299  STLDeleteValues(&old_apps);
300
301  FOR_EACH_OBSERVER(KioskAppManagerObserver, observers_,
302                    OnKioskAppsSettingsChanged());
303}
304
305void KioskAppManager::Observe(int type,
306                              const content::NotificationSource& source,
307                              const content::NotificationDetails& details) {
308  DCHECK_EQ(chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED, type);
309  UpdateAppData();
310}
311
312void KioskAppManager::GetKioskAppIconCacheDir(base::FilePath* cache_dir) {
313  base::FilePath user_data_dir;
314  CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
315  *cache_dir = user_data_dir.AppendASCII(kIconCacheDir);
316}
317
318void KioskAppManager::OnKioskAppDataChanged(const std::string& app_id) {
319  FOR_EACH_OBSERVER(KioskAppManagerObserver,
320                    observers_,
321                    OnKioskAppDataChanged(app_id));
322}
323
324void KioskAppManager::OnKioskAppDataLoadFailure(const std::string& app_id) {
325  FOR_EACH_OBSERVER(KioskAppManagerObserver,
326                    observers_,
327                    OnKioskAppDataLoadFailure(app_id));
328  RemoveApp(app_id);
329}
330
331}  // namespace chromeos
332