// 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 <string> #include <vector> #include "base/strings/string16.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/signin/easy_unlock_screenlock_state_handler.h" #include "chrome/browser/signin/easy_unlock_service.h" #include "chrome/browser/signin/screenlock_bridge.h" #include "chrome/common/pref_names.h" #include "chrome/grit/generated_resources.h" #include "chrome/test/base/testing_pref_service_syncable.h" #include "components/pref_registry/pref_registry_syncable.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/l10n/l10n_util.h" namespace { // Resource URLs for icons used by EasyUnlockScreenlockStateHandler. const char kLockedIconURL[] = "chrome://theme/IDR_EASY_UNLOCK_LOCKED"; const char kUnlockedIconURL[] = "chrome://theme/IDR_EASY_UNLOCK_UNLOCKED"; const char kSpinnerIconURL[] = "chrome://theme/IDR_EASY_UNLOCK_SPINNER"; // The expected size of user pod custom icons set by // EasyUnlockScreenlockStateHandler. const int kExpectedIconSize = 27; // Checks if |input| string has any unreplaced placeholders. bool StringHasPlaceholders(const base::string16& input) { std::vector<size_t> offsets; std::vector<base::string16> subst; subst.push_back(base::string16()); base::string16 replaced = ReplaceStringPlaceholders(input, subst, &offsets); return !offsets.empty(); } // Fake lock handler to be used in these tests. class TestLockHandler : public ScreenlockBridge::LockHandler { public: explicit TestLockHandler(const std::string& user_email) : user_email_(user_email), show_icon_count_(0u), auth_type_(OFFLINE_PASSWORD) { } virtual ~TestLockHandler() {} // ScreenlockBridge::LockHandler implementation: virtual void ShowBannerMessage(const base::string16& message) OVERRIDE { ASSERT_FALSE(true) << "Should not be reached."; } virtual void ShowUserPodCustomIcon( const std::string& user_email, const ScreenlockBridge::UserPodCustomIconOptions& icon) OVERRIDE { ASSERT_EQ(user_email_, user_email); ++show_icon_count_; last_custom_icon_ = icon.ToDictionaryValue().Pass(); ValidateCustomIcon(); } virtual void HideUserPodCustomIcon(const std::string& user_email) OVERRIDE { ASSERT_EQ(user_email_, user_email); last_custom_icon_.reset(); } virtual void EnableInput() OVERRIDE { ASSERT_FALSE(true) << "Should not be reached."; } virtual void SetAuthType(const std::string& user_email, AuthType auth_type, const base::string16& auth_value) OVERRIDE { ASSERT_EQ(user_email_, user_email); // Generally, this is allowed, but EasyUnlockScreenlockStateHandler should // avoid resetting the same auth type. EXPECT_NE(auth_type_, auth_type); auth_type_ = auth_type; auth_value_ = auth_value; } virtual AuthType GetAuthType(const std::string& user_email) const OVERRIDE { EXPECT_EQ(user_email_, user_email); return auth_type_; } virtual void Unlock(const std::string& user_email) OVERRIDE { ASSERT_FALSE(true) << "Should not be reached."; } virtual void AttemptEasySignin(const std::string& user_email, const std::string& secret, const std::string& key_label) OVERRIDE { ASSERT_FALSE(true) << "Should not be reached."; } // Utility methods used by tests: // Gets last set auth value. base::string16 GetAuthValue() const { return auth_value_; } // Sets the auth value. void SetAuthValue(const base::string16& value) { auth_value_ = value; } // Returns the number of times an icon was shown since the last call to this // method. size_t GetAndResetShowIconCount() { size_t result = show_icon_count_; show_icon_count_ = 0u; return result; } // Whether the custom icon is set. bool HasCustomIcon() const { return last_custom_icon_; } // If custom icon is set, returns the icon's resource URL. // If there is no icon, or if it doesn't have a resource URL set, returns // an empty string. std::string GetCustomIconURL() const { std::string result; if (last_custom_icon_) last_custom_icon_->GetString("resourceUrl", &result); return result; } // Whether the custom icon is set and it has a tooltip. bool CustomIconHasTooltip() const { return last_custom_icon_ && last_custom_icon_->HasKey("tooltip"); } // Gets the custom icon's tooltip text, if one is set. base::string16 GetCustomIconTooltip() const { base::string16 result; if (last_custom_icon_) last_custom_icon_->GetString("tooltip.text", &result); return result; } // Whether the custom icon's tooltip should be autoshown. If the icon is not // set, or it doesn't have a tooltip, returns false. bool IsCustomIconTooltipAutoshown() const { bool result = false; if (last_custom_icon_) last_custom_icon_->GetBoolean("tooltip.autoshow", &result); return result; } // Returns the custom icon's opacity. If the icon is not set, a negative value // is returned. int GetCustomIconOpacity() const { int result = -1; if (last_custom_icon_) last_custom_icon_->GetInteger("opacity", &result); return result; } // Whether the custom icon is set and if has hardlock capability enabed. bool CustomIconHardlocksOnClick() const { bool result = false; if (last_custom_icon_) last_custom_icon_->GetBoolean("hardlockOnClick", &result); return result; } // Whether the custom icon is set and has an animation set. bool IsCustomIconAnimated() const { return last_custom_icon_ && last_custom_icon_->HasKey("animation"); } private: // Does some sanity checks on the last icon set by |ShowUserPodCustomIcon|. // It will cause a test failure if the icon is not valid. void ValidateCustomIcon() { ASSERT_TRUE(last_custom_icon_.get()); EXPECT_FALSE(last_custom_icon_->HasKey("data")); int height = 0; last_custom_icon_->GetInteger("size.height", &height); EXPECT_EQ(kExpectedIconSize, height); int width = 0; last_custom_icon_->GetInteger("size.width", &width); EXPECT_EQ(kExpectedIconSize, width); if (last_custom_icon_->HasKey("animation")) { int animation_resource_width = -1; EXPECT_TRUE(last_custom_icon_->GetInteger("animation.resourceWidth", &animation_resource_width)); EXPECT_GT(animation_resource_width, kExpectedIconSize); EXPECT_EQ(0, animation_resource_width % kExpectedIconSize); EXPECT_TRUE( last_custom_icon_->GetInteger("animation.frameLengthMs", NULL)); } if (last_custom_icon_->HasKey("tooltip")) { base::string16 tooltip; EXPECT_TRUE(last_custom_icon_->GetString("tooltip.text", &tooltip)); EXPECT_FALSE(tooltip.empty()); EXPECT_FALSE(StringHasPlaceholders(tooltip)); } } // The fake user email used in test. All methods called on |this| should be // associated with this user. const std::string user_email_; // The last icon set using |SetUserPodCustomIcon|. Call to // |HideUserPodcustomIcon| resets it. scoped_ptr<base::DictionaryValue> last_custom_icon_; size_t show_icon_count_; // Auth type and value set using |SetAuthType|. AuthType auth_type_; base::string16 auth_value_; DISALLOW_COPY_AND_ASSIGN(TestLockHandler); }; class EasyUnlockScreenlockStateHandlerTest : public testing::Test { public: EasyUnlockScreenlockStateHandlerTest() : user_email_("test_user@gmail.com") {} virtual ~EasyUnlockScreenlockStateHandlerTest() {} virtual void SetUp() OVERRIDE { pref_service_.reset(new TestingPrefServiceSyncable()); // The preference used to determine if easy unlock was previously used by // the user on the device ought to be registered by the EasyUnlockService. EasyUnlockService::RegisterProfilePrefs(pref_service_->registry()); // Create and inject fake lock handler to the screenlock bridge. lock_handler_.reset(new TestLockHandler(user_email_)); ScreenlockBridge* screenlock_bridge = ScreenlockBridge::Get(); screenlock_bridge->SetLockHandler(lock_handler_.get()); // Create the screenlock state handler object that will be tested. state_handler_.reset( new EasyUnlockScreenlockStateHandler(user_email_, pref_service_.get(), screenlock_bridge)); } virtual void TearDown() OVERRIDE { ScreenlockBridge::Get()->SetLockHandler(NULL); lock_handler_.reset(); state_handler_.reset(); } protected: // The state handler that is being tested. scoped_ptr<EasyUnlockScreenlockStateHandler> state_handler_; // The user associated with |state_handler_|. const std::string user_email_; // Faked lock handler given to ScreenlockBridge during the test. Abstracts // the screen lock UI. scoped_ptr<TestLockHandler> lock_handler_; // The user's preferences. scoped_ptr<TestingPrefServiceSyncable> pref_service_; }; TEST_F(EasyUnlockScreenlockStateHandlerTest, AuthenticatedInitialRun) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_EQ(kUnlockedIconURL, lock_handler_->GetCustomIconURL()); EXPECT_TRUE(lock_handler_->CustomIconHasTooltip()); EXPECT_TRUE(lock_handler_->IsCustomIconTooltipAutoshown()); EXPECT_FALSE(lock_handler_->CustomIconHardlocksOnClick()); EXPECT_FALSE(lock_handler_->IsCustomIconAnimated()); EXPECT_EQ(100, lock_handler_->GetCustomIconOpacity()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); // Duplicated state change should be ignored. EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, AuthenticatedNotInitialRun) { // Update preference for showing tutorial. pref_service_->SetBoolean(prefs::kEasyUnlockShowTutorial, false); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_EQ(kUnlockedIconURL, lock_handler_->GetCustomIconURL()); EXPECT_TRUE(lock_handler_->CustomIconHasTooltip()); EXPECT_FALSE(lock_handler_->IsCustomIconTooltipAutoshown()); EXPECT_TRUE(lock_handler_->CustomIconHardlocksOnClick()); EXPECT_FALSE(lock_handler_->IsCustomIconAnimated()); EXPECT_EQ(100, lock_handler_->GetCustomIconOpacity()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, BluetoothConnecting) { pref_service_->SetBoolean(prefs::kEasyUnlockShowTutorial, false); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_EQ(kSpinnerIconURL, lock_handler_->GetCustomIconURL()); EXPECT_FALSE(lock_handler_->CustomIconHasTooltip()); EXPECT_TRUE(lock_handler_->CustomIconHardlocksOnClick()); EXPECT_TRUE(lock_handler_->IsCustomIconAnimated()); EXPECT_EQ(100, lock_handler_->GetCustomIconOpacity()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); // Duplicated state change should be ignored. EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, StatesWithLockedIcon) { pref_service_->SetBoolean(prefs::kEasyUnlockShowTutorial, false); std::vector<EasyUnlockScreenlockStateHandler::State> states; states.push_back(EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH); states.push_back(EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); states.push_back(EasyUnlockScreenlockStateHandler::STATE_PHONE_UNSUPPORTED); states.push_back(EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE); states.push_back( EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED); states.push_back(EasyUnlockScreenlockStateHandler::STATE_PHONE_LOCKED); for (size_t i = 0; i < states.size(); ++i) { state_handler_->ChangeState(states[i]); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()) << "State: " << states[i]; EXPECT_EQ(ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)) << "State: " << states[i]; ASSERT_TRUE(lock_handler_->HasCustomIcon()) << "State: " << states[i]; EXPECT_EQ(kLockedIconURL, lock_handler_->GetCustomIconURL()) << "State: " << states[i]; EXPECT_TRUE(lock_handler_->CustomIconHasTooltip()) << "State: " << states[i]; EXPECT_FALSE(lock_handler_->IsCustomIconTooltipAutoshown()) << "State: " << states[i]; EXPECT_TRUE(lock_handler_->CustomIconHardlocksOnClick()) << "State: " << states[i]; EXPECT_FALSE(lock_handler_->IsCustomIconAnimated()) << "State: " << states[i]; EXPECT_EQ(100, lock_handler_->GetCustomIconOpacity()) << "State: " << states[i]; state_handler_->ChangeState(states[i]); EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()) << "State: " << states[i]; } } TEST_F(EasyUnlockScreenlockStateHandlerTest, LockScreenClearedOnStateHandlerDestruction) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); state_handler_.reset(); EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)); ASSERT_FALSE(lock_handler_->HasCustomIcon()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, StatePreservedWhenScreenUnlocks) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); ScreenlockBridge::Get()->SetLockHandler(NULL); lock_handler_.reset(new TestLockHandler(user_email_)); EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); ScreenlockBridge::Get()->SetLockHandler(lock_handler_.get()); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, StateChangeWhileScreenUnlocked) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); ScreenlockBridge::Get()->SetLockHandler(NULL); lock_handler_.reset(new TestLockHandler(user_email_)); EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); ScreenlockBridge::Get()->SetLockHandler(lock_handler_.get()); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_EQ(ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)); ASSERT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_TRUE(lock_handler_->IsCustomIconAnimated()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, HardlockEnabledAfterInitialUnlock) { std::vector<EasyUnlockScreenlockStateHandler::State> states; states.push_back( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); states.push_back( EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED); states.push_back(EasyUnlockScreenlockStateHandler::STATE_NO_BLUETOOTH); states.push_back(EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); states.push_back(EasyUnlockScreenlockStateHandler::STATE_PHONE_UNSUPPORTED); states.push_back(EasyUnlockScreenlockStateHandler::STATE_PHONE_UNLOCKABLE); // This one should go last as changing state to AUTHENTICATED enables hard // locking. states.push_back(EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); for (size_t i = 0; i < states.size(); ++i) { state_handler_->ChangeState(states[i]); ASSERT_TRUE(lock_handler_->HasCustomIcon()) << "State: " << states[i]; EXPECT_FALSE(lock_handler_->CustomIconHardlocksOnClick()) << "State: " << states[i]; } ScreenlockBridge::Get()->SetLockHandler(NULL); lock_handler_.reset(new TestLockHandler(user_email_)); ScreenlockBridge::Get()->SetLockHandler(lock_handler_.get()); for (size_t i = 0; i < states.size(); ++i) { state_handler_->ChangeState(states[i]); ASSERT_TRUE(lock_handler_->HasCustomIcon()) << "State: " << states[i]; EXPECT_TRUE(lock_handler_->CustomIconHardlocksOnClick()) << "State: " << states[i]; } } TEST_F(EasyUnlockScreenlockStateHandlerTest, InactiveStateHidesIcon) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); ASSERT_TRUE(lock_handler_->HasCustomIcon()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_INACTIVE); ASSERT_FALSE(lock_handler_->HasCustomIcon()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, AuthenticatedStateClearsPreviousAuthValue) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_INACTIVE); lock_handler_->SetAuthValue(base::ASCIIToUTF16("xxx")); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(l10n_util::GetStringUTF16( IDS_EASY_UNLOCK_SCREENLOCK_USER_POD_AUTH_VALUE), lock_handler_->GetAuthValue()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); EXPECT_EQ(base::string16(), lock_handler_->GetAuthValue()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, ChangingStateDoesNotAffectAuthValueIfAuthTypeDoesNotChange) { lock_handler_->SetAuthValue(base::ASCIIToUTF16("xxx")); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); EXPECT_EQ(base::ASCIIToUTF16("xxx"), lock_handler_->GetAuthValue()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_PHONE_NOT_AUTHENTICATED); EXPECT_EQ(base::ASCIIToUTF16("xxx"), lock_handler_->GetAuthValue()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_BLUETOOTH_CONNECTING); EXPECT_EQ(base::ASCIIToUTF16("xxx"), lock_handler_->GetAuthValue()); EXPECT_TRUE(lock_handler_->IsCustomIconAnimated()); } TEST_F(EasyUnlockScreenlockStateHandlerTest, StateChangesIgnoredIfHardlocked) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); lock_handler_->SetAuthType( user_email_, ScreenlockBridge::LockHandler::FORCE_OFFLINE_PASSWORD, base::string16()); lock_handler_->HideUserPodCustomIcon(user_email_); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); EXPECT_FALSE(lock_handler_->HasCustomIcon()); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_FALSE(lock_handler_->HasCustomIcon()); EXPECT_EQ(ScreenlockBridge::LockHandler::FORCE_OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)); } TEST_F(EasyUnlockScreenlockStateHandlerTest, LockScreenChangeableAfterHardlockUnlocked) { state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); lock_handler_->SetAuthType( user_email_, ScreenlockBridge::LockHandler::FORCE_OFFLINE_PASSWORD, base::string16()); lock_handler_->HideUserPodCustomIcon(user_email_); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_NO_PHONE); EXPECT_FALSE(lock_handler_->HasCustomIcon()); ScreenlockBridge::Get()->SetLockHandler(NULL); lock_handler_.reset(new TestLockHandler(user_email_)); EXPECT_EQ(0u, lock_handler_->GetAndResetShowIconCount()); ScreenlockBridge::Get()->SetLockHandler(lock_handler_.get()); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_EQ(ScreenlockBridge::LockHandler::OFFLINE_PASSWORD, lock_handler_->GetAuthType(user_email_)); state_handler_->ChangeState( EasyUnlockScreenlockStateHandler::STATE_AUTHENTICATED); EXPECT_EQ(1u, lock_handler_->GetAndResetShowIconCount()); EXPECT_TRUE(lock_handler_->HasCustomIcon()); EXPECT_EQ(ScreenlockBridge::LockHandler::USER_CLICK, lock_handler_->GetAuthType(user_email_)); EXPECT_TRUE(lock_handler_->CustomIconHardlocksOnClick()); } } // namespace