easy_unlock_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright 2014 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/signin/easy_unlock_service.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/logging.h"
10#include "base/memory/ref_counted.h"
11#include "base/metrics/field_trial.h"
12#include "base/prefs/pref_service.h"
13#include "base/prefs/scoped_user_pref_update.h"
14#include "base/values.h"
15#include "chrome/browser/extensions/component_loader.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/signin/easy_unlock_screenlock_state_handler.h"
19#include "chrome/browser/signin/easy_unlock_service_factory.h"
20#include "chrome/browser/signin/easy_unlock_service_observer.h"
21#include "chrome/browser/signin/easy_unlock_toggle_flow.h"
22#include "chrome/browser/signin/screenlock_bridge.h"
23#include "chrome/browser/ui/extensions/application_launch.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/pref_names.h"
26#include "components/pref_registry/pref_registry_syncable.h"
27#include "device/bluetooth/bluetooth_adapter.h"
28#include "device/bluetooth/bluetooth_adapter_factory.h"
29#include "extensions/browser/extension_system.h"
30#include "extensions/common/one_shot_event.h"
31#include "grit/browser_resources.h"
32
33#if defined(OS_CHROMEOS)
34#include "chrome/browser/chromeos/profiles/profile_helper.h"
35#include "components/user_manager/user_manager.h"
36#endif
37
38namespace {
39
40// Key name of the local device permit record dictonary in kEasyUnlockPairing.
41const char kKeyPermitAccess[] = "permitAccess";
42
43// Key name of the remote device list in kEasyUnlockPairing.
44const char kKeyDevices[] = "devices";
45
46// Key name of the phone public key in a device dictionary.
47const char kKeyPhoneId[] = "permitRecord.id";
48
49extensions::ComponentLoader* GetComponentLoader(
50    content::BrowserContext* context) {
51  extensions::ExtensionSystem* extension_system =
52      extensions::ExtensionSystem::Get(context);
53  ExtensionService* extension_service = extension_system->extension_service();
54  return extension_service->component_loader();
55}
56
57}  // namespace
58
59// static
60EasyUnlockService* EasyUnlockService::Get(Profile* profile) {
61  return EasyUnlockServiceFactory::GetForProfile(profile);
62}
63
64class EasyUnlockService::BluetoothDetector
65    : public device::BluetoothAdapter::Observer {
66 public:
67  explicit BluetoothDetector(EasyUnlockService* service)
68      : service_(service),
69        weak_ptr_factory_(this) {
70  }
71
72  virtual ~BluetoothDetector() {
73    if (adapter_)
74      adapter_->RemoveObserver(this);
75  }
76
77  void Initialize() {
78    if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable())
79      return;
80
81    device::BluetoothAdapterFactory::GetAdapter(
82        base::Bind(&BluetoothDetector::OnAdapterInitialized,
83                   weak_ptr_factory_.GetWeakPtr()));
84  }
85
86  bool IsPresent() const {
87    return adapter_ && adapter_->IsPresent();
88  }
89
90  // device::BluetoothAdapter::Observer:
91  virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter,
92                                     bool present) OVERRIDE {
93    service_->OnBluetoothAdapterPresentChanged();
94  }
95
96 private:
97  void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter) {
98    adapter_ = adapter;
99    adapter_->AddObserver(this);
100    service_->OnBluetoothAdapterPresentChanged();
101  }
102
103  // Owner of this class and should out-live this class.
104  EasyUnlockService* service_;
105  scoped_refptr<device::BluetoothAdapter> adapter_;
106  base::WeakPtrFactory<BluetoothDetector> weak_ptr_factory_;
107
108  DISALLOW_COPY_AND_ASSIGN(BluetoothDetector);
109};
110
111EasyUnlockService::EasyUnlockService(Profile* profile)
112    : profile_(profile),
113      bluetooth_detector_(new BluetoothDetector(this)),
114      turn_off_flow_status_(IDLE),
115      weak_ptr_factory_(this) {
116  extensions::ExtensionSystem::Get(profile_)->ready().Post(
117      FROM_HERE,
118      base::Bind(&EasyUnlockService::Initialize,
119                 weak_ptr_factory_.GetWeakPtr()));
120}
121
122EasyUnlockService::~EasyUnlockService() {
123}
124
125// static
126void EasyUnlockService::RegisterProfilePrefs(
127    user_prefs::PrefRegistrySyncable* registry) {
128  registry->RegisterBooleanPref(
129      prefs::kEasyUnlockEnabled,
130      false,
131      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
132  registry->RegisterBooleanPref(
133      prefs::kEasyUnlockShowTutorial,
134      true,
135      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
136  registry->RegisterDictionaryPref(
137      prefs::kEasyUnlockPairing,
138      new base::DictionaryValue(),
139      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
140  registry->RegisterBooleanPref(
141      prefs::kEasyUnlockAllowed,
142      true,
143      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
144}
145
146void EasyUnlockService::LaunchSetup() {
147  ExtensionService* service =
148      extensions::ExtensionSystem::Get(profile_)->extension_service();
149  const extensions::Extension* extension =
150      service->GetExtensionById(extension_misc::kEasyUnlockAppId, false);
151
152  OpenApplication(AppLaunchParams(
153      profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW, NEW_WINDOW));
154}
155
156bool EasyUnlockService::IsAllowed() {
157#if defined(OS_CHROMEOS)
158  if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser())
159    return false;
160
161  if (!chromeos::ProfileHelper::IsPrimaryProfile(profile_))
162    return false;
163
164  if (!profile_->GetPrefs()->GetBoolean(prefs::kEasyUnlockAllowed))
165    return false;
166
167  // It is disabled when the trial exists and is in "Disable" group.
168  if (base::FieldTrialList::FindFullName("EasyUnlock") == "Disable")
169    return false;
170
171  if (!bluetooth_detector_->IsPresent())
172    return false;
173
174  return true;
175#else
176  // TODO(xiyuan): Revisit when non-chromeos platforms are supported.
177  return false;
178#endif
179}
180
181EasyUnlockScreenlockStateHandler*
182    EasyUnlockService::GetScreenlockStateHandler() {
183  if (!IsAllowed())
184    return NULL;
185  if (!screenlock_state_handler_) {
186    screenlock_state_handler_.reset(new EasyUnlockScreenlockStateHandler(
187        ScreenlockBridge::GetAuthenticatedUserEmail(profile_),
188        profile_->GetPrefs(),
189        ScreenlockBridge::Get()));
190  }
191  return screenlock_state_handler_.get();
192}
193
194const base::DictionaryValue* EasyUnlockService::GetPermitAccess() const {
195  const base::DictionaryValue* pairing_dict =
196      profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing);
197  const base::DictionaryValue* permit_dict = NULL;
198  if (pairing_dict &&
199      pairing_dict->GetDictionary(kKeyPermitAccess, &permit_dict)) {
200    return permit_dict;
201  }
202
203  return NULL;
204}
205
206void EasyUnlockService::SetPermitAccess(const base::DictionaryValue& permit) {
207  DictionaryPrefUpdate pairing_update(profile_->GetPrefs(),
208                                      prefs::kEasyUnlockPairing);
209  pairing_update->SetWithoutPathExpansion(kKeyPermitAccess, permit.DeepCopy());
210}
211
212void EasyUnlockService::ClearPermitAccess() {
213  DictionaryPrefUpdate pairing_update(profile_->GetPrefs(),
214                                      prefs::kEasyUnlockPairing);
215  pairing_update->RemoveWithoutPathExpansion(kKeyPermitAccess, NULL);
216}
217
218const base::ListValue* EasyUnlockService::GetRemoteDevices() const {
219  const base::DictionaryValue* pairing_dict =
220      profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing);
221  const base::ListValue* devices = NULL;
222  if (pairing_dict && pairing_dict->GetList(kKeyDevices, &devices)) {
223    return devices;
224  }
225
226  return NULL;
227}
228
229void EasyUnlockService::SetRemoteDevices(const base::ListValue& devices) {
230  DictionaryPrefUpdate pairing_update(profile_->GetPrefs(),
231                                      prefs::kEasyUnlockPairing);
232  pairing_update->SetWithoutPathExpansion(kKeyDevices, devices.DeepCopy());
233}
234
235void EasyUnlockService::ClearRemoteDevices() {
236  DictionaryPrefUpdate pairing_update(profile_->GetPrefs(),
237                                      prefs::kEasyUnlockPairing);
238  pairing_update->RemoveWithoutPathExpansion(kKeyDevices, NULL);
239}
240
241void EasyUnlockService::AddObserver(EasyUnlockServiceObserver* observer) {
242  observers_.AddObserver(observer);
243}
244
245void EasyUnlockService::RemoveObserver(EasyUnlockServiceObserver* observer) {
246  observers_.RemoveObserver(observer);
247}
248
249void EasyUnlockService::RunTurnOffFlow() {
250  if (turn_off_flow_status_ == PENDING)
251    return;
252
253  SetTurnOffFlowStatus(PENDING);
254
255  // Currently there should only be one registered phone.
256  // TODO(xiyuan): Revisit this when server supports toggle for all or
257  // there are multiple phones.
258  const base::DictionaryValue* pairing_dict =
259      profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing);
260  const base::ListValue* devices_list = NULL;
261  const base::DictionaryValue* first_device = NULL;
262  std::string phone_public_key;
263  if (!pairing_dict || !pairing_dict->GetList(kKeyDevices, &devices_list) ||
264      !devices_list || !devices_list->GetDictionary(0, &first_device) ||
265      !first_device ||
266      !first_device->GetString(kKeyPhoneId, &phone_public_key)) {
267    LOG(WARNING) << "Bad easy unlock pairing data, wiping out local data";
268    OnTurnOffFlowFinished(true);
269    return;
270  }
271
272  turn_off_flow_.reset(new EasyUnlockToggleFlow(
273      profile_,
274      phone_public_key,
275      false,
276      base::Bind(&EasyUnlockService::OnTurnOffFlowFinished,
277                 base::Unretained(this))));
278  turn_off_flow_->Start();
279}
280
281void EasyUnlockService::ResetTurnOffFlow() {
282  turn_off_flow_.reset();
283  SetTurnOffFlowStatus(IDLE);
284}
285
286void EasyUnlockService::Initialize() {
287  registrar_.Init(profile_->GetPrefs());
288  registrar_.Add(
289      prefs::kEasyUnlockAllowed,
290      base::Bind(&EasyUnlockService::OnPrefsChanged, base::Unretained(this)));
291  OnPrefsChanged();
292
293#if defined(OS_CHROMEOS)
294  // Only start Bluetooth detection for ChromeOS since the feature is
295  // only offered on ChromeOS. Enabling this on non-ChromeOS platforms
296  // previously introduced a performance regression: http://crbug.com/404482
297  // Make sure not to reintroduce a performance regression if re-enabling on
298  // additional platforms.
299  // TODO(xiyuan): Revisit when non-chromeos platforms are supported.
300  bluetooth_detector_->Initialize();
301#endif  // defined(OS_CHROMEOS)
302}
303
304void EasyUnlockService::LoadApp() {
305  DCHECK(IsAllowed());
306
307#if defined(GOOGLE_CHROME_BUILD)
308  base::FilePath easy_unlock_path;
309#if defined(OS_CHROMEOS)
310  easy_unlock_path = base::FilePath("/usr/share/chromeos-assets/easy_unlock");
311#endif  // defined(OS_CHROMEOS)
312
313#ifndef NDEBUG
314  // Only allow app path override switch for debug build.
315  const CommandLine* command_line = CommandLine::ForCurrentProcess();
316  if (command_line->HasSwitch(switches::kEasyUnlockAppPath)) {
317    easy_unlock_path =
318        command_line->GetSwitchValuePath(switches::kEasyUnlockAppPath);
319  }
320#endif  // !defined(NDEBUG)
321
322  if (!easy_unlock_path.empty()) {
323    extensions::ComponentLoader* loader = GetComponentLoader(profile_);
324    if (!loader->Exists(extension_misc::kEasyUnlockAppId))
325      loader->Add(IDR_EASY_UNLOCK_MANIFEST, easy_unlock_path);
326  }
327#endif  // defined(GOOGLE_CHROME_BUILD)
328}
329
330void EasyUnlockService::UnloadApp() {
331  extensions::ComponentLoader* loader = GetComponentLoader(profile_);
332  if (loader->Exists(extension_misc::kEasyUnlockAppId))
333    loader->Remove(extension_misc::kEasyUnlockAppId);
334}
335
336void EasyUnlockService::UpdateAppState() {
337  if (IsAllowed()) {
338    LoadApp();
339  } else {
340    UnloadApp();
341    // Reset the screenlock state handler to make sure Screenlock state set
342    // by Easy Unlock app is reset.
343    screenlock_state_handler_.reset();
344  }
345}
346
347void EasyUnlockService::OnPrefsChanged() {
348  UpdateAppState();
349}
350
351void EasyUnlockService::OnBluetoothAdapterPresentChanged() {
352  UpdateAppState();
353}
354
355void EasyUnlockService::SetTurnOffFlowStatus(TurnOffFlowStatus status) {
356  turn_off_flow_status_ = status;
357  FOR_EACH_OBSERVER(
358      EasyUnlockServiceObserver, observers_, OnTurnOffOperationStatusChanged());
359}
360
361void EasyUnlockService::OnTurnOffFlowFinished(bool success) {
362  turn_off_flow_.reset();
363
364  if (!success) {
365    SetTurnOffFlowStatus(FAIL);
366    return;
367  }
368
369  ClearRemoteDevices();
370  SetTurnOffFlowStatus(IDLE);
371
372  // Make sure lock screen state set by the extension gets reset.
373  screenlock_state_handler_.reset();
374
375  if (GetComponentLoader(profile_)->Exists(extension_misc::kEasyUnlockAppId)) {
376    extensions::ExtensionSystem* extension_system =
377        extensions::ExtensionSystem::Get(profile_);
378    extension_system->extension_service()->ReloadExtension(
379        extension_misc::kEasyUnlockAppId);
380  }
381}
382