| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/signin/easy_unlock_service.h" |
| |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/component_loader.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/easy_unlock_screenlock_state_handler.h" |
| #include "chrome/browser/signin/easy_unlock_service_factory.h" |
| #include "chrome/browser/signin/easy_unlock_service_observer.h" |
| #include "chrome/browser/signin/easy_unlock_toggle_flow.h" |
| #include "chrome/browser/signin/screenlock_bridge.h" |
| #include "chrome/browser/ui/extensions/application_launch.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "extensions/common/one_shot_event.h" |
| #include "grit/browser_resources.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/power_manager_client.h" |
| #include "components/user_manager/user_manager.h" |
| #endif |
| |
| namespace { |
| |
| // Key name of the local device permit record dictonary in kEasyUnlockPairing. |
| const char kKeyPermitAccess[] = "permitAccess"; |
| |
| // Key name of the remote device list in kEasyUnlockPairing. |
| const char kKeyDevices[] = "devices"; |
| |
| // Key name of the phone public key in a device dictionary. |
| const char kKeyPhoneId[] = "permitRecord.id"; |
| |
| extensions::ComponentLoader* GetComponentLoader( |
| content::BrowserContext* context) { |
| extensions::ExtensionSystem* extension_system = |
| extensions::ExtensionSystem::Get(context); |
| ExtensionService* extension_service = extension_system->extension_service(); |
| return extension_service->component_loader(); |
| } |
| |
| } // namespace |
| |
| // static |
| EasyUnlockService* EasyUnlockService::Get(Profile* profile) { |
| return EasyUnlockServiceFactory::GetForProfile(profile); |
| } |
| |
| class EasyUnlockService::BluetoothDetector |
| : public device::BluetoothAdapter::Observer { |
| public: |
| explicit BluetoothDetector(EasyUnlockService* service) |
| : service_(service), |
| weak_ptr_factory_(this) { |
| } |
| |
| virtual ~BluetoothDetector() { |
| if (adapter_) |
| adapter_->RemoveObserver(this); |
| } |
| |
| void Initialize() { |
| if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) |
| return; |
| |
| device::BluetoothAdapterFactory::GetAdapter( |
| base::Bind(&BluetoothDetector::OnAdapterInitialized, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| bool IsPresent() const { |
| return adapter_ && adapter_->IsPresent(); |
| } |
| |
| // device::BluetoothAdapter::Observer: |
| virtual void AdapterPresentChanged(device::BluetoothAdapter* adapter, |
| bool present) OVERRIDE { |
| service_->OnBluetoothAdapterPresentChanged(); |
| } |
| |
| private: |
| void OnAdapterInitialized(scoped_refptr<device::BluetoothAdapter> adapter) { |
| adapter_ = adapter; |
| adapter_->AddObserver(this); |
| service_->OnBluetoothAdapterPresentChanged(); |
| } |
| |
| // Owner of this class and should out-live this class. |
| EasyUnlockService* service_; |
| scoped_refptr<device::BluetoothAdapter> adapter_; |
| base::WeakPtrFactory<BluetoothDetector> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BluetoothDetector); |
| }; |
| |
| #if defined(OS_CHROMEOS) |
| class EasyUnlockService::PowerMonitor : |
| public chromeos::PowerManagerClient::Observer { |
| public: |
| explicit PowerMonitor(EasyUnlockService* service) : service_(service) { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| AddObserver(this); |
| } |
| |
| virtual ~PowerMonitor() { |
| chromeos::DBusThreadManager::Get()->GetPowerManagerClient()-> |
| RemoveObserver(this); |
| } |
| |
| private: |
| // chromeos::PowerManagerClient::Observer: |
| virtual void SuspendImminent() OVERRIDE { |
| service_->DisableAppIfLoaded(); |
| service_->screenlock_state_handler_.reset(); |
| } |
| |
| virtual void SuspendDone(const base::TimeDelta& sleep_duration) OVERRIDE { |
| service_->LoadApp(); |
| } |
| |
| EasyUnlockService* service_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PowerMonitor); |
| }; |
| #endif |
| |
| EasyUnlockService::EasyUnlockService(Profile* profile) |
| : profile_(profile), |
| bluetooth_detector_(new BluetoothDetector(this)), |
| turn_off_flow_status_(IDLE), |
| weak_ptr_factory_(this) { |
| extensions::ExtensionSystem::Get(profile_)->ready().Post( |
| FROM_HERE, |
| base::Bind(&EasyUnlockService::Initialize, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| EasyUnlockService::~EasyUnlockService() { |
| } |
| |
| // static |
| void EasyUnlockService::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref( |
| prefs::kEasyUnlockEnabled, |
| false, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kEasyUnlockShowTutorial, |
| true, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| registry->RegisterDictionaryPref( |
| prefs::kEasyUnlockPairing, |
| new base::DictionaryValue(), |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| registry->RegisterBooleanPref( |
| prefs::kEasyUnlockAllowed, |
| true, |
| user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); |
| } |
| |
| void EasyUnlockService::LaunchSetup() { |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(profile_)->extension_service(); |
| const extensions::Extension* extension = |
| service->GetExtensionById(extension_misc::kEasyUnlockAppId, false); |
| |
| OpenApplication(AppLaunchParams( |
| profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW, NEW_WINDOW)); |
| } |
| |
| bool EasyUnlockService::IsAllowed() { |
| #if defined(OS_CHROMEOS) |
| if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser()) |
| return false; |
| |
| if (!chromeos::ProfileHelper::IsPrimaryProfile(profile_)) |
| return false; |
| |
| if (!profile_->GetPrefs()->GetBoolean(prefs::kEasyUnlockAllowed)) |
| return false; |
| |
| // Respect existing policy and skip finch test. |
| if (!profile_->GetPrefs()->IsManagedPreference(prefs::kEasyUnlockAllowed)) { |
| // It is disabled when the trial exists and is in "Disable" group. |
| if (base::FieldTrialList::FindFullName("EasyUnlock") == "Disable") |
| return false; |
| } |
| |
| if (!bluetooth_detector_->IsPresent()) |
| return false; |
| |
| return true; |
| #else |
| // TODO(xiyuan): Revisit when non-chromeos platforms are supported. |
| return false; |
| #endif |
| } |
| |
| EasyUnlockScreenlockStateHandler* |
| EasyUnlockService::GetScreenlockStateHandler() { |
| if (!IsAllowed()) |
| return NULL; |
| if (!screenlock_state_handler_) { |
| screenlock_state_handler_.reset(new EasyUnlockScreenlockStateHandler( |
| ScreenlockBridge::GetAuthenticatedUserEmail(profile_), |
| profile_->GetPrefs(), |
| ScreenlockBridge::Get())); |
| } |
| return screenlock_state_handler_.get(); |
| } |
| |
| const base::DictionaryValue* EasyUnlockService::GetPermitAccess() const { |
| const base::DictionaryValue* pairing_dict = |
| profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing); |
| const base::DictionaryValue* permit_dict = NULL; |
| if (pairing_dict && |
| pairing_dict->GetDictionary(kKeyPermitAccess, &permit_dict)) { |
| return permit_dict; |
| } |
| |
| return NULL; |
| } |
| |
| void EasyUnlockService::SetPermitAccess(const base::DictionaryValue& permit) { |
| DictionaryPrefUpdate pairing_update(profile_->GetPrefs(), |
| prefs::kEasyUnlockPairing); |
| pairing_update->SetWithoutPathExpansion(kKeyPermitAccess, permit.DeepCopy()); |
| } |
| |
| void EasyUnlockService::ClearPermitAccess() { |
| DictionaryPrefUpdate pairing_update(profile_->GetPrefs(), |
| prefs::kEasyUnlockPairing); |
| pairing_update->RemoveWithoutPathExpansion(kKeyPermitAccess, NULL); |
| } |
| |
| const base::ListValue* EasyUnlockService::GetRemoteDevices() const { |
| const base::DictionaryValue* pairing_dict = |
| profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing); |
| const base::ListValue* devices = NULL; |
| if (pairing_dict && pairing_dict->GetList(kKeyDevices, &devices)) { |
| return devices; |
| } |
| |
| return NULL; |
| } |
| |
| void EasyUnlockService::SetRemoteDevices(const base::ListValue& devices) { |
| DictionaryPrefUpdate pairing_update(profile_->GetPrefs(), |
| prefs::kEasyUnlockPairing); |
| pairing_update->SetWithoutPathExpansion(kKeyDevices, devices.DeepCopy()); |
| } |
| |
| void EasyUnlockService::ClearRemoteDevices() { |
| DictionaryPrefUpdate pairing_update(profile_->GetPrefs(), |
| prefs::kEasyUnlockPairing); |
| pairing_update->RemoveWithoutPathExpansion(kKeyDevices, NULL); |
| } |
| |
| void EasyUnlockService::AddObserver(EasyUnlockServiceObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void EasyUnlockService::RemoveObserver(EasyUnlockServiceObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void EasyUnlockService::RunTurnOffFlow() { |
| if (turn_off_flow_status_ == PENDING) |
| return; |
| |
| SetTurnOffFlowStatus(PENDING); |
| |
| // Currently there should only be one registered phone. |
| // TODO(xiyuan): Revisit this when server supports toggle for all or |
| // there are multiple phones. |
| const base::DictionaryValue* pairing_dict = |
| profile_->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing); |
| const base::ListValue* devices_list = NULL; |
| const base::DictionaryValue* first_device = NULL; |
| std::string phone_public_key; |
| if (!pairing_dict || !pairing_dict->GetList(kKeyDevices, &devices_list) || |
| !devices_list || !devices_list->GetDictionary(0, &first_device) || |
| !first_device || |
| !first_device->GetString(kKeyPhoneId, &phone_public_key)) { |
| LOG(WARNING) << "Bad easy unlock pairing data, wiping out local data"; |
| OnTurnOffFlowFinished(true); |
| return; |
| } |
| |
| turn_off_flow_.reset(new EasyUnlockToggleFlow( |
| profile_, |
| phone_public_key, |
| false, |
| base::Bind(&EasyUnlockService::OnTurnOffFlowFinished, |
| base::Unretained(this)))); |
| turn_off_flow_->Start(); |
| } |
| |
| void EasyUnlockService::ResetTurnOffFlow() { |
| turn_off_flow_.reset(); |
| SetTurnOffFlowStatus(IDLE); |
| } |
| |
| void EasyUnlockService::Initialize() { |
| registrar_.Init(profile_->GetPrefs()); |
| registrar_.Add( |
| prefs::kEasyUnlockAllowed, |
| base::Bind(&EasyUnlockService::OnPrefsChanged, base::Unretained(this))); |
| OnPrefsChanged(); |
| |
| #if defined(OS_CHROMEOS) |
| // Only start Bluetooth detection for ChromeOS since the feature is |
| // only offered on ChromeOS. Enabling this on non-ChromeOS platforms |
| // previously introduced a performance regression: http://crbug.com/404482 |
| // Make sure not to reintroduce a performance regression if re-enabling on |
| // additional platforms. |
| // TODO(xiyuan): Revisit when non-chromeos platforms are supported. |
| bluetooth_detector_->Initialize(); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| void EasyUnlockService::LoadApp() { |
| DCHECK(IsAllowed()); |
| |
| #if defined(GOOGLE_CHROME_BUILD) |
| base::FilePath easy_unlock_path; |
| #if defined(OS_CHROMEOS) |
| easy_unlock_path = base::FilePath("/usr/share/chromeos-assets/easy_unlock"); |
| #endif // defined(OS_CHROMEOS) |
| |
| #ifndef NDEBUG |
| // Only allow app path override switch for debug build. |
| const CommandLine* command_line = CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kEasyUnlockAppPath)) { |
| easy_unlock_path = |
| command_line->GetSwitchValuePath(switches::kEasyUnlockAppPath); |
| } |
| #endif // !defined(NDEBUG) |
| |
| if (!easy_unlock_path.empty()) { |
| extensions::ComponentLoader* loader = GetComponentLoader(profile_); |
| if (!loader->Exists(extension_misc::kEasyUnlockAppId)) { |
| loader->Add(IDR_EASY_UNLOCK_MANIFEST, easy_unlock_path); |
| } |
| ExtensionService* extension_service = |
| extensions::ExtensionSystem::Get(profile_)->extension_service(); |
| extension_service->EnableExtension(extension_misc::kEasyUnlockAppId); |
| } |
| #endif // defined(GOOGLE_CHROME_BUILD) |
| } |
| |
| void EasyUnlockService::DisableAppIfLoaded() { |
| extensions::ComponentLoader* loader = GetComponentLoader(profile_); |
| if (!loader->Exists(extension_misc::kEasyUnlockAppId)) |
| return; |
| |
| ExtensionService* extension_service = |
| extensions::ExtensionSystem::Get(profile_)->extension_service(); |
| extension_service->DisableExtension(extension_misc::kEasyUnlockAppId, |
| extensions::Extension::DISABLE_RELOAD); |
| } |
| |
| void EasyUnlockService::UpdateAppState() { |
| if (IsAllowed()) { |
| LoadApp(); |
| |
| #if defined(OS_CHROMEOS) |
| if (!power_monitor_) |
| power_monitor_.reset(new PowerMonitor(this)); |
| #endif |
| } else { |
| DisableAppIfLoaded(); |
| // Reset the screenlock state handler to make sure Screenlock state set |
| // by Easy Unlock app is reset. |
| screenlock_state_handler_.reset(); |
| |
| #if defined(OS_CHROMEOS) |
| power_monitor_.reset(); |
| #endif |
| } |
| } |
| |
| void EasyUnlockService::OnPrefsChanged() { |
| UpdateAppState(); |
| } |
| |
| void EasyUnlockService::OnBluetoothAdapterPresentChanged() { |
| UpdateAppState(); |
| } |
| |
| void EasyUnlockService::SetTurnOffFlowStatus(TurnOffFlowStatus status) { |
| turn_off_flow_status_ = status; |
| FOR_EACH_OBSERVER( |
| EasyUnlockServiceObserver, observers_, OnTurnOffOperationStatusChanged()); |
| } |
| |
| void EasyUnlockService::OnTurnOffFlowFinished(bool success) { |
| turn_off_flow_.reset(); |
| |
| if (!success) { |
| SetTurnOffFlowStatus(FAIL); |
| return; |
| } |
| |
| ClearRemoteDevices(); |
| SetTurnOffFlowStatus(IDLE); |
| |
| // Make sure lock screen state set by the extension gets reset. |
| screenlock_state_handler_.reset(); |
| |
| if (GetComponentLoader(profile_)->Exists(extension_misc::kEasyUnlockAppId)) { |
| extensions::ExtensionSystem* extension_system = |
| extensions::ExtensionSystem::Get(profile_); |
| extension_system->extension_service()->ReloadExtension( |
| extension_misc::kEasyUnlockAppId); |
| } |
| } |