diff options
21 files changed, 1039 insertions, 112 deletions
diff --git a/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc b/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc index a7f1ce6..1b7f87e 100644 --- a/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc +++ b/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.cc @@ -10,6 +10,9 @@ #include "base/values.h" #include "chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_bluetooth_util.h" #include "chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_crypto_delegate.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/easy_unlock_screenlock_state_handler.h" +#include "chrome/browser/signin/easy_unlock_service.h" #include "chrome/common/extensions/api/easy_unlock_private.h" #include "extensions/browser/browser_context_keyed_api_factory.h" #include "grit/generated_resources.h" @@ -34,6 +37,36 @@ EasyUnlockPrivateCryptoDelegate* GetCryptoDelegate( ->crypto_delegate(); } +EasyUnlockScreenlockStateHandler* GetScreenlockStateHandler( + content::BrowserContext* context) { + return EasyUnlockService::Get(Profile::FromBrowserContext(context)) + ->GetScreenlockStateHandler(); +} + +EasyUnlockScreenlockStateHandler::State ToScreenlockStateHandlerState( + easy_unlock_private::State state) { + switch (state) { + case easy_unlock_private::STATE_NO_BLUETOOTH: + return EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH; + case easy_unlock_private::STATE_BLUETOOTH_CONNECTING: + return EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING; + case easy_unlock_private::STATE_NO_PHONE: + return EasyUnlockScreenlockStateHandler::STATE_NO_PHONE; + case easy_unlock_private::STATE_PHONE_NOT_AUTHENTICATED: + return EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED; + case easy_unlock_private::STATE_PHONE_LOCKED: + return EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED; + case easy_unlock_private::STATE_PHONE_UNLOCKABLE: + return EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE; + case easy_unlock_private::STATE_PHONE_NOT_NEARBY: + return EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_NEARBY; + case easy_unlock_private::STATE_AUTHENTICATED: + return EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED; + default: + return EasyUnlockScreenlockStateHandler::STATE_INACTIVE; + } +} + } // namespace // static @@ -368,5 +401,28 @@ void EasyUnlockPrivateSeekBluetoothDeviceByAddressFunction::OnSeekCompleted( } } +EasyUnlockPrivateUpdateScreenlockStateFunction:: + EasyUnlockPrivateUpdateScreenlockStateFunction() {} + +EasyUnlockPrivateUpdateScreenlockStateFunction:: + ~EasyUnlockPrivateUpdateScreenlockStateFunction() {} + +bool EasyUnlockPrivateUpdateScreenlockStateFunction::RunSync() { + scoped_ptr<easy_unlock_private::UpdateScreenlockState::Params> params( + easy_unlock_private::UpdateScreenlockState::Params::Create(*args_)); + EXTENSION_FUNCTION_VALIDATE(params.get()); + + EasyUnlockScreenlockStateHandler* screenlock_state_handler = + GetScreenlockStateHandler(browser_context()); + if (screenlock_state_handler) { + screenlock_state_handler->ChangeState( + ToScreenlockStateHandlerState(params->state)); + return true; + } + + SetError("Not allowed"); + return false; +} + } // namespace api } // namespace extensions diff --git a/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.h b/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.h index de47c5e..3802861 100644 --- a/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.h +++ b/chrome/browser/extensions/api/easy_unlock_private/easy_unlock_private_api.h @@ -81,6 +81,8 @@ class EasyUnlockPrivatePerformECDHKeyAgreementFunction DECLARE_EXTENSION_FUNCTION("easyUnlockPrivate.performECDHKeyAgreement", EASYUNLOCKPRIVATE_PERFORMECDHKEYAGREEMENT) + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockPrivatePerformECDHKeyAgreementFunction); }; class EasyUnlockPrivateGenerateEcP256KeyPairFunction @@ -99,6 +101,8 @@ class EasyUnlockPrivateGenerateEcP256KeyPairFunction DECLARE_EXTENSION_FUNCTION("easyUnlockPrivate.generateEcP256KeyPair", EASYUNLOCKPRIVATE_GENERATEECP256KEYPAIR) + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockPrivateGenerateEcP256KeyPairFunction); }; class EasyUnlockPrivateCreateSecureMessageFunction @@ -116,6 +120,8 @@ class EasyUnlockPrivateCreateSecureMessageFunction DECLARE_EXTENSION_FUNCTION("easyUnlockPrivate.createSecureMessage", EASYUNLOCKPRIVATE_CREATESECUREMESSAGE) + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockPrivateCreateSecureMessageFunction); }; class EasyUnlockPrivateUnwrapSecureMessageFunction @@ -133,6 +139,8 @@ class EasyUnlockPrivateUnwrapSecureMessageFunction DECLARE_EXTENSION_FUNCTION("easyUnlockPrivate.unwrapSecureMessage", EASYUNLOCKPRIVATE_UNWRAPSECUREMESSAGE) + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockPrivateUnwrapSecureMessageFunction); }; class EasyUnlockPrivateSeekBluetoothDeviceByAddressFunction @@ -155,6 +163,23 @@ class EasyUnlockPrivateSeekBluetoothDeviceByAddressFunction EasyUnlockPrivateSeekBluetoothDeviceByAddressFunction); }; +class EasyUnlockPrivateUpdateScreenlockStateFunction + : public SyncExtensionFunction { + public: + EasyUnlockPrivateUpdateScreenlockStateFunction(); + + protected: + virtual ~EasyUnlockPrivateUpdateScreenlockStateFunction(); + + virtual bool RunSync() OVERRIDE; + + private: + DECLARE_EXTENSION_FUNCTION("easyUnlockPrivate.updateScreenlockState", + EASYUNLOCKPRIVATE_UPDATESCREENLOCKSTATE) + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockPrivateUpdateScreenlockStateFunction); +}; + } // namespace api } // namespace extensions diff --git a/chrome/browser/extensions/api/screenlock_private/screenlock_private_api.cc b/chrome/browser/extensions/api/screenlock_private/screenlock_private_api.cc index a51aca6..eb7b6e2 100644 --- a/chrome/browser/extensions/api/screenlock_private/screenlock_private_api.cc +++ b/chrome/browser/extensions/api/screenlock_private/screenlock_private_api.cc @@ -7,6 +7,7 @@ #include <vector> #include "base/lazy_instance.h" +#include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/screenlock_private.h" @@ -100,7 +101,7 @@ bool ScreenlockPrivateShowMessageFunction::RunAsync() { ScreenlockBridge::LockHandler* locker = ScreenlockBridge::Get()->lock_handler(); if (locker) - locker->ShowBannerMessage(params->message); + locker->ShowBannerMessage(base::UTF8ToUTF16(params->message)); SendResponse(error_.empty()); return true; } @@ -168,9 +169,17 @@ void ScreenlockPrivateShowCustomIconFunction::OnImageLoaded( const gfx::Image& image) { ScreenlockBridge::LockHandler* locker = ScreenlockBridge::Get()->lock_handler(); + if (!locker) { + SetError(kNotLockedError); + SendResponse(false); + return; + } + + ScreenlockBridge::UserPodCustomIconOptions icon; + icon.SetIconAsImage(image); locker->ShowUserPodCustomIcon( ScreenlockBridge::GetAuthenticatedUserEmail(GetProfile()), - image); + icon); SendResponse(error_.empty()); } @@ -212,7 +221,7 @@ bool ScreenlockPrivateSetAuthTypeFunction::RunAsync() { locker->SetAuthType( ScreenlockBridge::GetAuthenticatedUserEmail(GetProfile()), ToLockHandlerAuthType(params->auth_type), - initial_value); + base::UTF8ToUTF16(initial_value)); } else { SetError(kNotLockedError); } diff --git a/chrome/browser/signin/easy_unlock_screenlock_state_handler.cc b/chrome/browser/signin/easy_unlock_screenlock_state_handler.cc new file mode 100644 index 0000000..13816fc --- /dev/null +++ b/chrome/browser/signin/easy_unlock_screenlock_state_handler.cc @@ -0,0 +1,211 @@ +// 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_screenlock_state_handler.h" + +#include "base/bind.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/chromeos/chromeos_utils.h" +#include "chrome/common/pref_names.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +size_t kIconSize = 27u; +size_t kOpaqueIconOpacity = 50u; +size_t kSpinnerResourceWidth = 1215u; +size_t kSpinnerIntervalMs = 50u; + +std::string GetIconURLForState(EasyUnlockScreenlockStateHandler::State state) { + switch (state) { + case EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH: + case EasyUnlockScreenlockStateHandler::STATE_NO_PHONE: + case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED: + case EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED: + case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_NEARBY: + case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE: + return "chrome://theme/IDR_EASY_UNLOCK_LOCKED"; + case EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING: + return "chrome://theme/IDR_EASY_UNLOCK_SPINNER"; + case EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED: + return "chrome://theme/IDR_EASY_UNLOCK_UNLOCKED"; + default: + return ""; + } +} + +bool UseOpaqueIcon(EasyUnlockScreenlockStateHandler::State state) { + return state == EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH || + state == EasyUnlockScreenlockStateHandler::STATE_NO_PHONE || + state == EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_NEARBY || + state == EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE; +} + +bool HasAnimation(EasyUnlockScreenlockStateHandler::State state) { + return state == EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING; +} + +size_t GetTooltipResourceId(EasyUnlockScreenlockStateHandler::State state) { + switch (state) { + case EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_BLUETOOTH; + case EasyUnlockScreenlockStateHandler::STATE_NO_PHONE: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_NO_PHONE; + case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_NOT_AUTHENTICATED; + case EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_LOCKED; + case EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_UNLOCKABLE; + case EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_NEARBY: + return IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_PHONE_NOT_NEARBY; + case EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED: + // TODO(tbarzic): When hard lock is enabled change this to + // IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_HARDLOCK_INSTRUCTIONS. + return 0; + default: + return 0; + } +} + +} // namespace + + +EasyUnlockScreenlockStateHandler::EasyUnlockScreenlockStateHandler( + const std::string& user_email, + PrefService* pref_service, + ScreenlockBridge* screenlock_bridge) + : state_(STATE_INACTIVE), + user_email_(user_email), + pref_service_(pref_service), + screenlock_bridge_(screenlock_bridge) { + DCHECK(screenlock_bridge_); + screenlock_bridge_->AddObserver(this); +} + +EasyUnlockScreenlockStateHandler::~EasyUnlockScreenlockStateHandler() { + screenlock_bridge_->RemoveObserver(this); + // Make sure the screenlock state set by this gets cleared. + ChangeState(STATE_INACTIVE); +} + +void EasyUnlockScreenlockStateHandler::ChangeState(State new_state) { + if (state_ == new_state) + return; + + state_ = new_state; + + // If lock screen is not active, just cache the current state. + // The screenlock state will get refreshed in |ScreenDidLock|. + if (!screenlock_bridge_->IsLocked()) + return; + + UpdateScreenlockAuthType(); + + ScreenlockBridge::UserPodCustomIconOptions icon_options; + + std::string icon_url = GetIconURLForState(state_); + if (icon_url.empty()) { + screenlock_bridge_->lock_handler()->HideUserPodCustomIcon(user_email_); + return; + } + icon_options.SetIconAsResourceURL(icon_url); + + UpdateTooltipOptions(&icon_options); + + if (UseOpaqueIcon(state_)) + icon_options.SetOpacity(kOpaqueIconOpacity); + + icon_options.SetSize(kIconSize, kIconSize); + + if (HasAnimation(state_)) + icon_options.SetAnimation(kSpinnerResourceWidth, kSpinnerIntervalMs); + + screenlock_bridge_->lock_handler()->ShowUserPodCustomIcon(user_email_, + icon_options); +} + +void EasyUnlockScreenlockStateHandler::OnScreenDidLock() { + State last_state = state_; + // This should force updating screenlock state. + state_ = STATE_INACTIVE; + ChangeState(last_state); +} + +void EasyUnlockScreenlockStateHandler::OnScreenDidUnlock() { +} + +void EasyUnlockScreenlockStateHandler::UpdateTooltipOptions( + ScreenlockBridge::UserPodCustomIconOptions* icon_options) { + bool show_tutorial = ShouldShowTutorial(); + + size_t resource_id = 0; + base::string16 device_name; + if (show_tutorial) { + resource_id = IDS_EASY_UNLOCK_SCREENLOCK_TOOLTIP_TUTORIAL; + } else { + resource_id = GetTooltipResourceId(state_); + if (state_ == STATE_AUTHENTICATED || state_ == STATE_PHONE_UNLOCKABLE) + device_name = GetDeviceName(); + } + + if (!resource_id) + return; + + base::string16 tooltip; + if (device_name.empty()) { + tooltip = l10n_util::GetStringUTF16(resource_id); + } else { + tooltip = l10n_util::GetStringFUTF16(resource_id, device_name); + } + + if (tooltip.empty()) + return; + + if (show_tutorial) + MarkTutorialShown(); + + icon_options->SetTooltip(tooltip, show_tutorial /* autoshow tooltip */); +} + +bool EasyUnlockScreenlockStateHandler::ShouldShowTutorial() { + if (state_ != STATE_AUTHENTICATED) + return false; + return pref_service_ && + pref_service_->GetBoolean(prefs::kEasyUnlockShowTutorial); +} + +void EasyUnlockScreenlockStateHandler::MarkTutorialShown() { + if (!pref_service_) + return; + pref_service_->SetBoolean(prefs::kEasyUnlockShowTutorial, false); +} + +base::string16 EasyUnlockScreenlockStateHandler::GetDeviceName() { +#if defined(OS_CHROMEOS) + return chromeos::GetChromeDeviceType(); +#else + // TODO(tbarzic): Figure out the name for non Chrome OS case. + return base::ASCIIToUTF16("Chrome"); +#endif +} + +void EasyUnlockScreenlockStateHandler::UpdateScreenlockAuthType() { + if (state_ == STATE_AUTHENTICATED) { + screenlock_bridge_->lock_handler()->SetAuthType( + user_email_, + ScreenlockBridge::LockHandler::USER_CLICK, + l10n_util::GetStringUTF16( + IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE)); + } else if (screenlock_bridge_->lock_handler()->GetAuthType(user_email_) != + ScreenlockBridge::LockHandler::OFFLINE_PASSWORD) { + screenlock_bridge_->lock_handler()->SetAuthType( + user_email_, + ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, + base::string16()); + } +} diff --git a/chrome/browser/signin/easy_unlock_screenlock_state_handler.h b/chrome/browser/signin/easy_unlock_screenlock_state_handler.h new file mode 100644 index 0000000..dc5c2cc --- /dev/null +++ b/chrome/browser/signin/easy_unlock_screenlock_state_handler.h @@ -0,0 +1,91 @@ +// 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. + +#ifndef CHROME_BROWSER_SIGNIN_EASY_UNLOCK_SCREENLOCK_STATE_HANDLER_H_ +#define CHROME_BROWSER_SIGNIN_EASY_UNLOCK_SCREENLOCK_STATE_HANDLER_H_ + +#include <string> + +#include "base/strings/string16.h" +#include "base/timer/timer.h" +#include "chrome/browser/signin/screenlock_bridge.h" + +class PrefService; + +// Profile specific class responsible for updating screenlock UI for the user +// associated with the profile when their Easy Unlock state changes. +class EasyUnlockScreenlockStateHandler : public ScreenlockBridge::Observer { + public: + // Available Easy Unlock states. + enum State { + // Easy Unlock is not enabled, or the screen is not locked. + STATE_INACTIVE, + // Bluetooth is not on. + STATE_NO_BLUETOOTH, + // Easy Unlock is in process of turning on Bluetooth. + STATE_BLUETOOTH_CONNECTING, + // No phones eligible to unlock the device can be found. + STATE_NO_PHONE, + // A phone eligible to unlock the device is found, but cannot be + // authenticated. + STATE_PHONE_NOT_AUTHENTICATED, + // A phone eligible to unlock the device is found, but it's locked. + STATE_PHONE_LOCKED, + // A phone eligible to unlock the device is found, but does not have lock + // screen enabled. + STATE_PHONE_UNLOCKABLE, + // A phone eligible to unlock the device is found, but it's not close enough + // to be allowed to unlock the device. + STATE_PHONE_NOT_NEARBY, + // The device can be unlocked using Easy Unlock. + STATE_AUTHENTICATED + }; + + // |user_email|: The email for the user associated with the profile to which + // this class is attached. + // |pref_service|: The profile preferences. + // |screenlock_bridge|: The screenlock bridge used to update the screen lock + // state. + EasyUnlockScreenlockStateHandler(const std::string& user_email, + PrefService* pref_service, + ScreenlockBridge* screenlock_bridge); + virtual ~EasyUnlockScreenlockStateHandler(); + + // Changes internal state to |new_state| and updates the user's screenlock + // accordingly. + void ChangeState(State new_state); + + private: + // ScreenlockBridge::Observer: + virtual void OnScreenDidLock() OVERRIDE; + virtual void OnScreenDidUnlock() OVERRIDE; + + void UpdateTooltipOptions( + ScreenlockBridge::UserPodCustomIconOptions* icon_options); + + // Whether the tutorial message should be shown to the user. The message is + // shown only once, when the user encounters STATE_AUTHENTICATED for the first + // time (across sessions). After the tutorial message is shown, + // |MarkTutorialShown| should be called to prevent further tutorial message. + bool ShouldShowTutorial(); + + // Sets user preference that prevents showing of tutorial messages. + void MarkTutorialShown(); + + // Gets the name to be used for the device. The name depends on the device + // type (example values: Chromebook and Chromebox). + base::string16 GetDeviceName(); + + // Updates the screenlock auth type if it has to be changed. + void UpdateScreenlockAuthType(); + + State state_; + std::string user_email_; + PrefService* pref_service_; + ScreenlockBridge* screenlock_bridge_; + + DISALLOW_COPY_AND_ASSIGN(EasyUnlockScreenlockStateHandler); +}; + +#endif // CHROME_BROWSER_SIGNIN_EASY_UNLOCK_SCREENLOCK_STATE_HANDLER_H_ diff --git a/chrome/browser/signin/easy_unlock_service.cc b/chrome/browser/signin/easy_unlock_service.cc index 1afcbc9..81d9f0e 100644 --- a/chrome/browser/signin/easy_unlock_service.cc +++ b/chrome/browser/signin/easy_unlock_service.cc @@ -13,7 +13,9 @@ #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/screenlock_bridge.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/pref_names.h" @@ -45,7 +47,8 @@ EasyUnlockService* EasyUnlockService::Get(Profile* profile) { } EasyUnlockService::EasyUnlockService(Profile* profile) - : profile_(profile), weak_ptr_factory_(this) { + : profile_(profile), + weak_ptr_factory_(this) { extensions::ExtensionSystem::Get(profile_)->ready().Post( FROM_HERE, base::Bind(&EasyUnlockService::Initialize, @@ -88,7 +91,7 @@ void EasyUnlockService::LaunchSetup() { bool EasyUnlockService::IsAllowed() { #if defined(OS_CHROMEOS) - if (chromeos::UserManager::Get()->IsLoggedInAsGuest()) + if (!chromeos::UserManager::Get()->IsLoggedInAsRegularUser()) return false; if (!chromeos::ProfileHelper::IsPrimaryProfile(profile_)) @@ -105,6 +108,20 @@ bool EasyUnlockService::IsAllowed() { #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(); +} + void EasyUnlockService::Initialize() { registrar_.Init(profile_->GetPrefs()); registrar_.Add( @@ -143,8 +160,12 @@ void EasyUnlockService::UnloadApp() { } void EasyUnlockService::OnPrefsChanged() { - if (IsAllowed()) + if (IsAllowed()) { LoadApp(); - else + } else { UnloadApp(); + // Reset the screenlock state handler to make sure Screenlock state set + // by Easy Unlock app is reset. + screenlock_state_handler_.reset(); + } } diff --git a/chrome/browser/signin/easy_unlock_service.h b/chrome/browser/signin/easy_unlock_service.h index dda962f..a8379f7 100644 --- a/chrome/browser/signin/easy_unlock_service.h +++ b/chrome/browser/signin/easy_unlock_service.h @@ -14,6 +14,7 @@ namespace user_prefs { class PrefRegistrySyncable; } +class EasyUnlockScreenlockStateHandler; class Profile; class EasyUnlockService : public KeyedService { @@ -35,6 +36,12 @@ class EasyUnlockService : public KeyedService { // permitted either the flag is enabled or its field trial is enabled. bool IsAllowed(); + // Gets |screenlock_state_handler_|. Returns NULL if Easy Unlock is not + // allowed. Otherwise, if |screenlock_state_handler_| is not set, an instance + // is created. Do not cache the returned value, as it may go away if Easy + // Unlock gets disabled. + EasyUnlockScreenlockStateHandler* GetScreenlockStateHandler(); + private: void Initialize(); void LoadApp(); @@ -43,6 +50,8 @@ class EasyUnlockService : public KeyedService { Profile* profile_; PrefChangeRegistrar registrar_; + // Created lazily in |GetScreenlockStateHandler|. + scoped_ptr<EasyUnlockScreenlockStateHandler> screenlock_state_handler_; base::WeakPtrFactory<EasyUnlockService> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(EasyUnlockService); diff --git a/chrome/browser/signin/screenlock_bridge.cc b/chrome/browser/signin/screenlock_bridge.cc index 995b1d9..56c2910 100644 --- a/chrome/browser/signin/screenlock_bridge.cc +++ b/chrome/browser/signin/screenlock_bridge.cc @@ -5,9 +5,13 @@ #include "chrome/browser/signin/screenlock_bridge.h" #include "base/logging.h" +#include "base/strings/string16.h" #include "chrome/browser/profiles/profile_window.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "components/signin/core/browser/signin_manager.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/gfx/image/image.h" +#include "ui/gfx/image/image_skia.h" #if defined(OS_CHROMEOS) #include "chromeos/dbus/dbus_thread_manager.h" @@ -26,6 +30,106 @@ ScreenlockBridge* ScreenlockBridge::Get() { return g_screenlock_bridge_bridge_instance.Pointer(); } +ScreenlockBridge::UserPodCustomIconOptions::UserPodCustomIconOptions() + : width_(0u), + height_(0u), + animation_set_(false), + animation_resource_width_(0u), + animation_frame_length_ms_(0u), + opacity_(100u), + autoshow_tooltip_(false) { +} + +ScreenlockBridge::UserPodCustomIconOptions::~UserPodCustomIconOptions() {} + +scoped_ptr<base::DictionaryValue> +ScreenlockBridge::UserPodCustomIconOptions::ToDictionaryValue() const { + scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue()); + if (!icon_image_ && icon_resource_url_.empty()) + return result.Pass(); + + if (icon_image_) { + gfx::ImageSkia icon_skia = icon_image_->AsImageSkia(); + base::DictionaryValue* icon_representations = new base::DictionaryValue(); + icon_representations->SetString( + "scale1x", + webui::GetBitmapDataUrl( + icon_skia.GetRepresentation(1.0f).sk_bitmap())); + icon_representations->SetString( + "scale2x", + webui::GetBitmapDataUrl( + icon_skia.GetRepresentation(2.0f).sk_bitmap())); + result->Set("data", icon_representations); + } else { + result->SetString("resourceUrl", icon_resource_url_); + } + + if (!tooltip_.empty()) { + base::DictionaryValue* tooltip_options = new base::DictionaryValue(); + tooltip_options->SetString("text", tooltip_); + tooltip_options->SetBoolean("autoshow", autoshow_tooltip_); + result->Set("tooltip", tooltip_options); + } + + base::DictionaryValue* size = new base::DictionaryValue(); + size->SetInteger("height", height_); + size->SetInteger("width", width_); + result->Set("size", size); + + result->SetInteger("opacity", opacity_); + + if (animation_set_) { + base::DictionaryValue* animation = new base::DictionaryValue(); + animation->SetInteger("resourceWidth", + animation_resource_width_); + animation->SetInteger("frameLengthMs", + animation_frame_length_ms_); + result->Set("animation", animation); + } + return result.Pass(); +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetIconAsResourceURL( + const std::string& url) { + DCHECK(!icon_image_); + + icon_resource_url_ = url; +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetIconAsImage( + const gfx::Image& image) { + DCHECK(icon_resource_url_.empty()); + + icon_image_.reset(new gfx::Image(image)); + SetSize(image.Width(), image.Height()); +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetSize(size_t icon_width, + size_t icon_height) { + width_ = icon_width; + height_ = icon_height; +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetAnimation( + size_t resource_width, + size_t frame_length_ms) { + animation_set_ = true; + animation_resource_width_ = resource_width; + animation_frame_length_ms_ = frame_length_ms; +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetOpacity(size_t opacity) { + DCHECK_LE(opacity, 100u); + opacity_ = opacity; +} + +void ScreenlockBridge::UserPodCustomIconOptions::SetTooltip( + const base::string16& tooltip, + bool autoshow) { + tooltip_ = tooltip; + autoshow_tooltip_ = autoshow; +} + // static std::string ScreenlockBridge::GetAuthenticatedUserEmail(Profile* profile) { // |profile| has to be a signed-in profile with SigninManager already diff --git a/chrome/browser/signin/screenlock_bridge.h b/chrome/browser/signin/screenlock_bridge.h index 1d75e30..72cec43 100644 --- a/chrome/browser/signin/screenlock_bridge.h +++ b/chrome/browser/signin/screenlock_bridge.h @@ -7,9 +7,13 @@ #include <string> +#include "base/basictypes.h" #include "base/lazy_instance.h" #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "base/observer_list.h" +#include "base/strings/string16.h" +#include "base/values.h" namespace gfx { class Image; @@ -32,6 +36,68 @@ class ScreenlockBridge { virtual ~Observer() {} }; + // Class containing parameters describing the custom icon that should be + // shown on a user's screen lock pod next to the input field. + class UserPodCustomIconOptions { + public: + UserPodCustomIconOptions(); + ~UserPodCustomIconOptions(); + + // Converts parameters to a dictionary values that can be sent to the + // screenlock web UI. + scoped_ptr<base::DictionaryValue> ToDictionaryValue() const; + + // Sets the icon as chrome://theme resource URL. + void SetIconAsResourceURL(const std::string& url); + + // Sets the icon as a gfx::Image. The image will be converted to set of data + // URLs for each icon representation. Use |SetIconAsResourceURL| instead of + // this. + // TODO(tbarzic): Remove this one once easy unlock app stops using + // screenlockPrivate.showCustomIcon. + void SetIconAsImage(const gfx::Image& image); + + // Sets the icon size. Has to be called if |SetIconAsResourceURL| was used + // to set the icon. For animated icon, this should be set to a single frame + // size, not the animation resource size. + void SetSize(size_t icon_width, size_t icon_height); + + // If the icon is supposed to be animated, sets the animation parameters. + // If set, it expects that the resource set using |SetIcon*| methods + // contains horizontally arranged ordered list of animation frames. + // Note that the icon size set in |SetSize| should be a single frame size. + // |resource_width|: Total animation resource width. + // |frame_length_ms|: Time for which a single animation frame is shown. + void SetAnimation(size_t resource_width, size_t frame_length_ms); + + // Sets the icon opacity. The values should be in <0, 100] interval, which + // will get scaled into <0, 1] interval. The default value is 100. + void SetOpacity(size_t opacity); + + // Sets the icon tooltip. If |autoshow| is set the tooltip is automatically + // shown with the icon. + void SetTooltip(const base::string16& tooltip, bool autoshow); + + private: + std::string icon_resource_url_; + scoped_ptr<gfx::Image> icon_image_; + + size_t width_; + size_t height_; + + bool animation_set_; + size_t animation_resource_width_; + size_t animation_frame_length_ms_; + + // The opacity should be in <0, 100] range. + size_t opacity_; + + base::string16 tooltip_; + bool autoshow_tooltip_; + + DISALLOW_COPY_AND_ASSIGN(UserPodCustomIconOptions); + }; + class LockHandler { public: // Supported authentication types. Keep in sync with the enum in @@ -45,11 +111,12 @@ class ScreenlockBridge { }; // Displays |message| in a banner on the lock screen. - virtual void ShowBannerMessage(const std::string& message) = 0; + virtual void ShowBannerMessage(const base::string16& message) = 0; // Shows a custom icon in the user pod on the lock screen. - virtual void ShowUserPodCustomIcon(const std::string& user_email, - const gfx::Image& icon) = 0; + virtual void ShowUserPodCustomIcon( + const std::string& user_email, + const UserPodCustomIconOptions& icon) = 0; // Hides the custom icon in user pod for a user. virtual void HideUserPodCustomIcon(const std::string& user_email) = 0; @@ -60,7 +127,7 @@ class ScreenlockBridge { // Set the authentication type to be used on the lock screen. virtual void SetAuthType(const std::string& user_email, AuthType auth_type, - const std::string& auth_value) = 0; + const base::string16& auth_value) = 0; // Returns the authentication type used for a user. virtual AuthType GetAuthType(const std::string& user_email) const = 0; diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc index 0a2c84f..b88861d 100644 --- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc +++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.cc @@ -5,6 +5,7 @@ #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" #include <algorithm> +#include <vector> #include "base/bind.h" #include "base/bind_helpers.h" @@ -74,8 +75,6 @@ #include "net/url_request/url_request_context_getter.h" #include "third_party/cros_system_api/dbus/service_constants.h" #include "ui/base/webui/web_ui_util.h" -#include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" #if defined(USE_AURA) #include "ash/shell.h" @@ -411,8 +410,6 @@ void SigninScreenHandler::DeclareLocalizedValues( builder->Add("confirmPasswordText", IDS_LOGIN_CONFIRM_PASSWORD_TEXT); builder->Add("confirmPasswordErrorText", IDS_LOGIN_CONFIRM_PASSWORD_ERROR_TEXT); - builder->Add("easyUnlockTooltip", - IDS_LOGIN_EASY_UNLOCK_TOOLTIP); builder->Add("fatalEnrollmentError", IDS_ENTERPRISE_ENROLLMENT_AUTH_FATAL_ERROR); @@ -513,8 +510,8 @@ void SigninScreenHandler::UpdateUIState(UIState ui_state, } } -// TODO (ygorshenin@): split this method into small parts. -// TODO (ygorshenin@): move this logic to GaiaScreenHandler. +// TODO(ygorshenin@): split this method into small parts. +// TODO(ygorshenin@): move this logic to GaiaScreenHandler. void SigninScreenHandler::UpdateStateInternal( ErrorScreenActor::ErrorReason reason, bool force_update) { @@ -923,37 +920,17 @@ void SigninScreenHandler::Observe(int type, } } -void SigninScreenHandler::ShowBannerMessage(const std::string& message) { +void SigninScreenHandler::ShowBannerMessage(const base::string16& message) { CallJS("login.AccountPickerScreen.showBannerMessage", message); } void SigninScreenHandler::ShowUserPodCustomIcon( const std::string& username, - const gfx::Image& icon) { - gfx::ImageSkia icon_skia = icon.AsImageSkia(); - base::DictionaryValue icon_representations; - icon_representations.SetString( - "scale1x", - webui::GetBitmapDataUrl(icon_skia.GetRepresentation(1.0f).sk_bitmap())); - icon_representations.SetString( - "scale2x", - webui::GetBitmapDataUrl(icon_skia.GetRepresentation(2.0f).sk_bitmap())); - CallJS("login.AccountPickerScreen.showUserPodCustomIcon", - username, icon_representations); - - // TODO(tengs): Move this code once we move unlocking to native code. - if (ScreenLocker::default_screen_locker()) { - UserManager* user_manager = UserManager::Get(); - const user_manager::User* user = user_manager->FindUser(username); - if (!user) - return; - PrefService* profile_prefs = - ProfileHelper::Get()->GetProfileByUserUnsafe(user)->GetPrefs(); - if (profile_prefs->GetBoolean(prefs::kEasyUnlockShowTutorial)) { - CallJS("login.AccountPickerScreen.showEasyUnlockBubble"); - profile_prefs->SetBoolean(prefs::kEasyUnlockShowTutorial, false); - } - } + const ScreenlockBridge::UserPodCustomIconOptions& icon_options) { + scoped_ptr<base::DictionaryValue> icon = icon_options.ToDictionaryValue(); + if (!icon || icon->empty()) + return; + CallJS("login.AccountPickerScreen.showUserPodCustomIcon", username, *icon); } void SigninScreenHandler::HideUserPodCustomIcon(const std::string& username) { @@ -968,7 +945,7 @@ void SigninScreenHandler::EnableInput() { void SigninScreenHandler::SetAuthType( const std::string& username, ScreenlockBridge::LockHandler::AuthType auth_type, - const std::string& initial_value) { + const base::string16& initial_value) { delegate_->SetAuthType(username, auth_type); CallJS("login.AccountPickerScreen.setAuthType", diff --git a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h index 00804c9..2d8ffab 100644 --- a/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h +++ b/chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h @@ -312,14 +312,15 @@ class SigninScreenHandler const content::NotificationDetails& details) OVERRIDE; // ScreenlockBridge::LockHandler implementation: - virtual void ShowBannerMessage(const std::string& message) OVERRIDE; - virtual void ShowUserPodCustomIcon(const std::string& username, - const gfx::Image& icon) OVERRIDE; + virtual void ShowBannerMessage(const base::string16& message) OVERRIDE; + virtual void ShowUserPodCustomIcon( + const std::string& username, + const ScreenlockBridge::UserPodCustomIconOptions& icon) OVERRIDE; virtual void HideUserPodCustomIcon(const std::string& username) OVERRIDE; virtual void EnableInput() OVERRIDE; virtual void SetAuthType(const std::string& username, ScreenlockBridge::LockHandler::AuthType auth_type, - const std::string& initial_value) OVERRIDE; + const base::string16& initial_value) OVERRIDE; virtual ScreenlockBridge::LockHandler::AuthType GetAuthType( const std::string& username) const OVERRIDE; virtual void Unlock(const std::string& user_email) OVERRIDE; @@ -480,7 +481,7 @@ class SigninScreenHandler base::Closure kiosk_enable_flow_aborted_callback_for_test_; // Non-owning ptr. - // TODO (ygorshenin@): remove this dependency. + // TODO(ygorshenin@): remove this dependency. GaiaScreenHandler* gaia_screen_handler_; // Helper that retrieves the authenticated user's e-mail address. diff --git a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc index b88e631..4850162 100644 --- a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc +++ b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc @@ -217,7 +217,8 @@ UserManagerScreenHandler::~UserManagerScreenHandler() { ScreenlockBridge::Get()->SetLockHandler(NULL); } -void UserManagerScreenHandler::ShowBannerMessage(const std::string& message) { +void UserManagerScreenHandler::ShowBannerMessage( + const base::string16& message) { web_ui()->CallJavascriptFunction( "login.AccountPickerScreen.showBannerMessage", base::StringValue(message)); @@ -225,19 +226,14 @@ void UserManagerScreenHandler::ShowBannerMessage(const std::string& message) { void UserManagerScreenHandler::ShowUserPodCustomIcon( const std::string& user_email, - const gfx::Image& icon) { - gfx::ImageSkia icon_skia = icon.AsImageSkia(); - base::DictionaryValue icon_representations; - icon_representations.SetString( - "scale1x", - webui::GetBitmapDataUrl(icon_skia.GetRepresentation(1.0f).sk_bitmap())); - icon_representations.SetString( - "scale2x", - webui::GetBitmapDataUrl(icon_skia.GetRepresentation(2.0f).sk_bitmap())); + const ScreenlockBridge::UserPodCustomIconOptions& icon_options) { + scoped_ptr<base::DictionaryValue> icon = icon_options.ToDictionaryValue(); + if (!icon || icon->empty()) + return; web_ui()->CallJavascriptFunction( "login.AccountPickerScreen.showUserPodCustomIcon", base::StringValue(user_email), - icon_representations); + *icon); } void UserManagerScreenHandler::HideUserPodCustomIcon( @@ -254,7 +250,7 @@ void UserManagerScreenHandler::EnableInput() { void UserManagerScreenHandler::SetAuthType( const std::string& user_email, ScreenlockBridge::LockHandler::AuthType auth_type, - const std::string& auth_value) { + const base::string16& auth_value) { user_auth_type_map_[user_email] = auth_type; web_ui()->CallJavascriptFunction( "login.AccountPickerScreen.setAuthType", diff --git a/chrome/browser/ui/webui/signin/user_manager_screen_handler.h b/chrome/browser/ui/webui/signin/user_manager_screen_handler.h index afc801a..8339d61f 100644 --- a/chrome/browser/ui/webui/signin/user_manager_screen_handler.h +++ b/chrome/browser/ui/webui/signin/user_manager_screen_handler.h @@ -38,14 +38,15 @@ class UserManagerScreenHandler : public content::WebUIMessageHandler, void GetLocalizedValues(base::DictionaryValue* localized_strings); // ScreenlockBridge::LockHandler implementation. - virtual void ShowBannerMessage(const std::string& message) OVERRIDE; - virtual void ShowUserPodCustomIcon(const std::string& user_email, - const gfx::Image& icon) OVERRIDE; + virtual void ShowBannerMessage(const base::string16& message) OVERRIDE; + virtual void ShowUserPodCustomIcon( + const std::string& user_email, + const ScreenlockBridge::UserPodCustomIconOptions& icon_options) OVERRIDE; virtual void HideUserPodCustomIcon(const std::string& user_email) OVERRIDE; virtual void EnableInput() OVERRIDE; virtual void SetAuthType(const std::string& user_email, ScreenlockBridge::LockHandler::AuthType auth_type, - const std::string& auth_value) OVERRIDE; + const base::string16& auth_value) OVERRIDE; virtual ScreenlockBridge::LockHandler::AuthType GetAuthType( const std::string& user_email) const OVERRIDE; virtual void Unlock(const std::string& user_email) OVERRIDE; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index b8e67b7..bc6f200 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1536,6 +1536,8 @@ 'browser/search/hotword_service.h', 'browser/search/hotword_service_factory.cc', 'browser/search/hotword_service_factory.h', + 'browser/signin/easy_unlock_screenlock_state_handler.cc', + 'browser/signin/easy_unlock_screenlock_state_handler.h', 'browser/signin/easy_unlock_service.cc', 'browser/signin/easy_unlock_service.h', 'browser/signin/easy_unlock_service_factory.cc', diff --git a/chrome/common/extensions/api/easy_unlock_private.idl b/chrome/common/extensions/api/easy_unlock_private.idl index 9f43c2f..32e6410 100644 --- a/chrome/common/extensions/api/easy_unlock_private.idl +++ b/chrome/common/extensions/api/easy_unlock_private.idl @@ -18,6 +18,32 @@ AES_256_CBC }; + // Available states for the Easy Unlock app. + enum State { + // Screen is either not locked, or the Easy Unlock is not enabled. + INACTIVE, + // The Bluetooth is not enabled. + NO_BLUETOOTH, + // Bluetooth is being activated. + BLUETOOTH_CONNECTING, + // There are no phones eligible to unlock the device. + NO_PHONE, + // A phone eligible to unlock the device is detected, but can't be + // authenticated. + PHONE_NOT_AUTHENTICATED, + // A phone eligible to unlock the device is detected, but it's locked and + // thus unable to unlock the device. + PHONE_LOCKED, + // A phone eligible to unlock the device is detected, but it is not allowed + // to unlock the device because it doesn't have lock screen enabled. + PHONE_UNLOCKABLE, + // A phone eligible to unlock the device is detected, but it's not close + // enough to be allowed to unlock the device. + PHONE_NOT_NEARBY, + // The devie can be unlocked using Easy Unlock. + AUTHENTICATED + }; + // Options that can be passed to |unwrapSecureMessage| method. dictionary UnwrapSecureMessageOptions { // The data associated with the message. For the message to be succesfully @@ -145,5 +171,9 @@ // |callback|: Called to indicate success or failure. static void seekBluetoothDeviceByAddress(DOMString deviceAddress, optional EmptyCallback callback); + + // Updates the screenlock state to reflect the Easy Unlock app state. + static void updateScreenlockState(State state, + optional EmptyCallback callback); }; }; diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h index d980632..a46a700 100644 --- a/extensions/browser/extension_function_histogram_value.h +++ b/extensions/browser/extension_function_histogram_value.h @@ -934,6 +934,7 @@ enum HistogramValue { BROWSINGDATA_REMOVESERVICEWORKERS, USBPRIVATE_GETDEVICES, USBPRIVATE_GETDEVICEINFO, + EASYUNLOCKPRIVATE_UPDATESCREENLOCKSTATE, // Last entry: Add new entries above and ensure to update // tools/metrics/histograms/histograms/histograms.xml. ENUM_BOUNDARY diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 5965a50..8fd4b113 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -40594,6 +40594,7 @@ Therefore, the affected-histogram name has to have at least one dot in it. <int value="873" label="BROWSINGDATA_REMOVESERVICEWORKERS"/> <int value="874" label="USBPRIVATE_GETDEVICES"/> <int value="875" label="USBPRIVATE_GETDEVICEINFO"/> + <int value="876" label="EASYUNLOCKPRIVATE_UPDATESCREENLOCKSTATE"/> </enum> <enum name="ExtensionInstallCause" type="int"> diff --git a/ui/login/account_picker/screen_account_picker.js b/ui/login/account_picker/screen_account_picker.js index d4af56b..69e922a 100644 --- a/ui/login/account_picker/screen_account_picker.js +++ b/ui/login/account_picker/screen_account_picker.js @@ -29,7 +29,6 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { 'showUserPodCustomIcon', 'hideUserPodCustomIcon', 'setAuthType', - 'showEasyUnlockBubble', 'setPublicSessionDisplayName', 'setPublicSessionLocales', 'setPublicSessionKeyboardLayouts', @@ -279,8 +278,14 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { * Shows a custom icon in the user pod of |username|. This function * is used by the chrome.screenlockPrivate API. * @param {string} username Username of pod to add button - * @param {{scale1x: string, scale2x: string}} icon Dictionary of URLs of - * the custom icon's representations for 1x and 2x scale factors. + * @param {!{resourceUrl: (string | undefined), + * data: ({scale1x: string, scale2x: string} | undefined), + * size: ({width: number, height: number} | undefined), + * animation: ({resourceWidth: number, frameLength: number} | + * undefined), + * opacity: (number | undefined), + * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon + * The icon parameters. */ showUserPodCustomIcon: function(username, icon) { $('pod-row').showUserPodCustomIcon(username, icon); @@ -308,13 +313,6 @@ login.createScreen('AccountPickerScreen', 'account-picker', function() { }, /** - * Shows a tooltip bubble explaining Easy Unlock. - */ - showEasyUnlockBubble: function() { - $('pod-row').showEasyUnlockBubble(); - }, - - /** * Updates the display name shown on a public session pod. * @param {string} userID The user ID of the public session * @param {string} displayName The new display name diff --git a/ui/login/account_picker/user_pod_row.css b/ui/login/account_picker/user_pod_row.css index 5927b25..01a3b7a 100644 --- a/ui/login/account_picker/user_pod_row.css +++ b/ui/login/account_picker/user_pod_row.css @@ -219,10 +219,19 @@ html[dir=rtl] .main-pane { } .custom-icon { + -webkit-margin-end: 0; + -webkit-margin-start: auto; background-position: center; background-repeat: no-repeat; flex: none; +} + +.custom-icon-container { + display: flex; + flex: none; + flex-direction: column; height: 40px; + justify-content: center; width: 40px; } @@ -423,10 +432,6 @@ html[dir=rtl] .user-type-bubble { margin-bottom: 14px; } -.easy-unlock-button-content { - width: 145px; -} - /**** Public account user pod rules *******************************************/ .pod.public-account.expanded { diff --git a/ui/login/account_picker/user_pod_row.js b/ui/login/account_picker/user_pod_row.js index 194a3c6..39b5c7d 100644 --- a/ui/login/account_picker/user_pod_row.js +++ b/ui/login/account_picker/user_pod_row.js @@ -55,6 +55,7 @@ cr.define('login', function() { var DESKTOP_POD_HEIGHT = 226; var POD_ROW_PADDING = 10; var DESKTOP_ROW_PADDING = 15; + var CUSTOM_ICON_CONTAINER_SIZE = 40; /** * Minimal padding between user pod and virtual keyboard. @@ -159,6 +160,330 @@ cr.define('login', function() { } /** + * Creates an element for custom icon shown in a user pod next to the input + * field. + * @constructor + * @extends {HTMLDivElement} + */ + var UserPodCustomIcon = cr.ui.define(function() { + var node = document.createElement('div'); + node.classList.add('custom-icon-container'); + node.hidden = true; + + // Create the actual icon element and add it as a child to the container. + var iconNode = document.createElement('div'); + iconNode.classList.add('custom-icon'); + node.appendChild(iconNode); + return node; + }); + + UserPodCustomIcon.prototype = { + __proto__: HTMLDivElement.prototype, + + /** + * The icon height. + * @type {number} + * @private + */ + height_: 0, + + /** + * The icon width. + * @type {number} + * @private + */ + width_: 0, + + /** + * Tooltip to be shown when the user hovers over the icon. The icon + * properties may be set so the tooltip is shown automatically when the icon + * is updated. The tooltip is shown in a bubble attached to the icon + * element. + * @type {string} + * @private + */ + tooltip_: '', + + /** + * Whether the tooltip is shown and the user is hovering over the icon. + * @type {boolean} + * @private + */ + tooltipActive_: false, + + /** + * Whether the icon has been shown as a result of |autoshow| parameter begin + * set rather than user hovering over the icon. + * If this is set, the tooltip will not be removed when the mouse leaves the + * icon. + * @type {boolean} + * @private + */ + tooltipAutoshown_: false, + + /** + * A reference to the timeout for showing tooltip after mouse enters the + * icon. + * @type {?number} + * @private + */ + showTooltipTimeout_: null, + + /** + * If animation is set, the current horizontal background offset for the + * icon resource. + * @type {number} + * @private + */ + lastAnimationOffset_: 0, + + /** + * The reference to interval for progressing the animation. + * @type {?number} + * @private + */ + animationInterval_: null, + + /** + * The width of the resource that contains representations for different + * animation stages. + * @type {number} + * @private + */ + animationResourceSize_: 0, + + /** @override */ + decorate: function() { + this.iconElement.addEventListener('mouseover', + this.showTooltipSoon_.bind(this)); + this.iconElement.addEventListener('mouseout', + this.hideTooltip_.bind(this, false)); + this.iconElement.addEventListener('mousedown', + this.hideTooltip_.bind(this, false)); + }, + + /** + * Getter for the icon element's div. + * @return {HTMLDivElement} + */ + get iconElement() { + return this.querySelector('.custom-icon'); + }, + + /** + * Set the icon's background image as image set with different + * representations for available screen scale factors. + * @param {!{scale1x: string, scale2x: string}} icon The icon + * representations. + */ + setIconAsImageSet: function(icon) { + this.iconElement.style.backgroundImage = + '-webkit-image-set(' + + 'url(' + icon.scale1x + ') 1x,' + + 'url(' + icon.scale2x + ') 2x)'; + }, + + /** + * Sets the icon background image to a chrome://theme URL. + * @param {!string} iconUrl The icon's background image URL. + */ + setIconAsResourceUrl: function(iconUrl) { + this.iconElement.style.backgroundImage = + '-webkit-image-set(' + + 'url(' + iconUrl + '@1x) 1x,' + + 'url(' + iconUrl + '@2x) 2x)'; + }, + + /** + * Shows the icon. + */ + show: function() { + this.hidden = false; + }, + + /** + * Hides the icon. Makes sure the tooltip is hidden and animation reset. + */ + hide: function() { + this.hideTooltip_(true); + this.setAnimation(null); + this.hidden = true; + }, + + /** + * Sets the icon size element size. + * @param {!{width: number, height: number}} size The icon size. + */ + setSize: function(size) { + this.height_ = size.height < CUSTOM_ICON_CONTAINER_SIZE ? + size.height : CUSTOM_ICON_COTAINER_SIZE; + this.iconElement.style.height = this.height_ + 'px'; + + this.width_ = size.width < CUSTOM_ICON_CONTAINER_SIZE ? + size.width : CUSTOM_ICON_COTAINER_SIZE; + this.iconElement.style.width = this.width_ + 'px'; + this.style.width = this.width_ + 'px'; + }, + + /** + * Sets the icon opacity. + * @param {number} opacity The icon opacity in [0-100] scale. + */ + setOpacity: function(opacity) { + if (opacity > 100) { + this.style.opacity = 1; + } else if (opacity < 0) { + this.style.opacity = 0; + } else { + this.style.opacity = opacity / 100; + } + }, + + /** + * Updates the icon tooltip. If {@code autoshow} parameter is set the + * tooltip is immediatelly shown. If tooltip text is not set, the method + * ensures the tooltip gets hidden. If tooltip is shown prior to this call, + * it remains shown, but the tooltip text is updated. + * @param {!{text: string, autoshow: boolean}} tooltip The tooltip + * parameters. + */ + setTooltip: function(tooltip) { + if (this.tooltip_ == tooltip.text && !tooltip.autoshow) + return; + this.tooltip_ = tooltip.text; + + // If tooltip is already shown, just update the text. + if (tooltip.text && this.tooltipActive_ && !$('bubble').hidden) { + // If both previous and the new tooltip are supposed to be shown + // automatically, keep the autoshown flag. + var markAutoshown = this.tooltipAutoshown_ && tooltip.autoshow; + this.hideTooltip_(true); + this.showTooltip_(); + this.tooltipAutoshown_ = markAutoshown; + return; + } + + // If autoshow flag is set, make sure the tooltip gets shown. + if (tooltip.text && tooltip.autoshow) { + this.hideTooltip_(true); + this.showTooltip_(); + this.tooltipAutoshown_ = true; + // Reset the tooltip active flag, which gets set in |showTooltip_|. + this.tooltipActive_ = false; + return; + } + + this.hideTooltip_(true); + + if (this.tooltip_) + this.iconElement.setAttribute('aria-lablel', this.tooltip_); + }, + + /** + * Sets the icon animation parameter and starts the animation. + * The animation is set using the resource containing all animation frames + * concatenated horizontally. The animator offsets the background image in + * regural intervals. + * @param {?{resourceWidth: number, frameLengthMs: number}} animation + * |resourceWidth|: Total width for the resource containing the + * animation frames. + * |frameLengthMs|: Time interval for which a single animation frame is + * shown. + */ + setAnimation: function(animation) { + if (this.animationInterval_) + clearInterval(this.animationInterval_); + this.iconElement.style.backgroundPosition = 'center'; + if (!animation) + return; + this.lastAnimationOffset_ = 0; + this.animationResourceSize_ = animation.resourceWidth; + this.animationInterval_ = setInterval(this.progressAnimation_.bind(this), + animation.frameLengthMs); + }, + + /** + * Called when mouse enters the icon. It sets timeout for showing the + * tooltip. + * @private + */ + showTooltipSoon_: function() { + if (this.showTooltipTimeout_ || this.tooltipActive_) + return; + this.showTooltipTimeout_ = + setTimeout(this.showTooltip_.bind(this), 1000); + }, + + /** + * Shows the current tooltip, if one is set. + * @private + */ + showTooltip_: function() { + if (this.hidden || !this.tooltip_ || this.tooltipActive_) + return; + + // If autoshown bubble got hidden, clear the autoshown flag. + if ($('bubble').hidden && this.tooltipAutoshown_) + this.tooltipAutoshown_ = false; + + // Show the tooltip bubble. + var bubbleContent = document.createElement('div'); + bubbleContent.textContent = this.tooltip_; + + /** @const */ var BUBBLE_OFFSET = CUSTOM_ICON_CONTAINER_SIZE / 2; + /** @const */ var BUBBLE_PADDING = 8; + $('bubble').showContentForElement(this, + cr.ui.Bubble.Attachment.RIGHT, + bubbleContent, + BUBBLE_OFFSET, + BUBBLE_PADDING); + this.ensureTooltipTimeoutCleared_(); + this.tooltipActive_ = true; + }, + + /** + * Hides the tooltip. If the current tooltip was automatically shown, it + * will be hidden only if |force| is set. + * @param {boolean} Whether the tooltip should be hidden if it got shown + * because autoshow flag was set. + * @private + */ + hideTooltip_: function(force) { + this.tooltipActive_ = false; + this.ensureTooltipTimeoutCleared_(); + if (!force && this.tooltipAutoshown_) + return; + $('bubble').hideForElement(this); + this.tooltipAutoshown_ = false; + this.iconElement.removeAttribute('aria-label'); + }, + + /** + * Clears timaout for showing the tooltip if it's set. + * @private + */ + ensureTooltipTimeoutCleared_: function() { + if (this.showTooltipTimeout_) { + clearTimeout(this.showTooltipTimeout_); + this.showTooltipTimeout_ = null; + } + }, + + /** + * Horizontally offsets the animated icon's background for a single icon + * size width. + * @private + */ + progressAnimation_: function() { + this.lastAnimationOffset_ += this.width_; + if (this.lastAnimationOffset_ >= this.animationResourceSize_) + this.lastAnimationOffset_ = 0; + this.iconElement.style.backgroundPosition = + '-' + this.lastAnimationOffset_ + 'px center'; + } + }; + + /** * Unique salt added to user image URLs to prevent caching. Dictionary with * user names as keys. * @type {Object} @@ -198,6 +523,9 @@ cr.define('login', function() { this.actionBoxRemoveUserWarningButtonElement.addEventListener( 'keydown', this.handleRemoveUserConfirmationKeyDown_.bind(this)); + + var customIcon = this.customIconElement; + customIcon.parentNode.replaceChild(new UserPodCustomIcon(), customIcon); }, /** @@ -445,7 +773,7 @@ cr.define('login', function() { * @type {!HTMLDivElement} */ get customIconElement() { - return this.querySelector('.custom-icon'); + return this.querySelector('.custom-icon-container'); }, /** @@ -1843,8 +2171,14 @@ cr.define('login', function() { /** * Shows a custom icon on a user pod besides the input field. * @param {string} username Username of pod to add button - * @param {{scale1x: string, scale2x: string}} icon Dictionary of URLs of - * the custom icon's representations for 1x and 2x scale factors. + * @param {!{resourceUrl: (string | undefined), + * data: ({scale1x: string, scale2x: string} | undefined), + * size: ({width: number, height: number} | undefined), + * animation: ({resourceWidth: number, frameLength: number} | + * undefined), + * opacity: (number | undefined), + * tooltip: ({text: string, autoshow: boolean} | undefined)}} icon + * The icon parameters. */ showUserPodCustomIcon: function(username, icon) { var pod = this.getPodWithUsername_(username); @@ -1854,11 +2188,22 @@ cr.define('login', function() { return; } - pod.customIconElement.hidden = false; - pod.customIconElement.style.backgroundImage = - '-webkit-image-set(' + - 'url(' + icon.scale1x + ') 1x,' + - 'url(' + icon.scale2x + ') 2x)'; + if (icon.resourceUrl) { + pod.customIconElement.setIconAsResourceUrl(icon.resourceUrl); + } else if (icon.data) { + pod.customIconElement.setIconAsImageSet(icon.data); + } else { + return; + } + + pod.customIconElement.setSize(icon.size || {width: 0, height: 0}); + pod.customIconElement.setAnimation(icon.animation || null); + pod.customIconElement.setOpacity(icon.opacity || 100); + pod.customIconElement.show(); + // This has to be called after |show| in case the tooltip should be shown + // immediatelly. + pod.customIconElement.setTooltip( + icon.tooltip || {text: '', autoshow: false}); }, /** @@ -1873,7 +2218,7 @@ cr.define('login', function() { return; } - pod.customIconElement.hidden = true; + pod.customIconElement.hide(); }, /** @@ -1894,29 +2239,6 @@ cr.define('login', function() { }, /** - * Shows a tooltip bubble explaining Easy Unlock for the focused pod. - */ - showEasyUnlockBubble: function() { - if (!this.focusedPod_) { - console.error('No focused pod to show Easy Unlock bubble.'); - return; - } - - var bubbleContent = document.createElement('div'); - bubbleContent.classList.add('easy-unlock-button-content'); - bubbleContent.textContent = loadTimeData.getString('easyUnlockTooltip'); - - var attachElement = this.focusedPod_.customIconElement; - /** @const */ var BUBBLE_OFFSET = 20; - /** @const */ var BUBBLE_PADDING = 8; - $('bubble').showContentForElement(attachElement, - cr.ui.Bubble.Attachment.RIGHT, - bubbleContent, - BUBBLE_OFFSET, - BUBBLE_PADDING); - }, - - /** * Updates the display name shown on a public session pod. * @param {string} userID The user ID of the public session * @param {string} displayName The new display name diff --git a/ui/login/account_picker/user_pod_template.html b/ui/login/account_picker/user_pod_template.html index b61f42a..555c56c 100644 --- a/ui/login/account_picker/user_pod_template.html +++ b/ui/login/account_picker/user_pod_template.html @@ -28,7 +28,7 @@ </div> <!-- User Click Authentication --> <div class="password-label"></div> - <div class="custom-icon" hidden></div> + <div class="custom-icon-container" hidden></div> <div class="signin-button-container"> <button class="signin-button" i18n-content="signinButton"></button> </div> |