Skip to content

Commit

Permalink
ambient: Add access token controller
Browse files Browse the repository at this point in the history
This patch adds an access token controller for ambient mode. It will
refresh the token when entering lock screen. The following requests to
backdrop server will reuse the cached token before it expires.

Bug: b/148463064
Test: new unittests
Change-Id: Icf882b2d0938e29e06900b4bbffea8cdc5ba1af2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2149948
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: Xiyuan Xia <xiyuan@chromium.org>
Reviewed-by: Xiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#763547}
  • Loading branch information
wutao authored and Commit Bot committed Apr 28, 2020
1 parent e260e9e commit 942fe1f
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 15 deletions.
2 changes: 2 additions & 0 deletions ash/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ component("ash") {
"accessibility/touch_exploration_controller.h",
"accessibility/touch_exploration_manager.cc",
"accessibility/touch_exploration_manager.h",
"ambient/ambient_access_token_controller.cc",
"ambient/ambient_access_token_controller.h",
"ambient/ambient_constants.h",
"ambient/ambient_controller.cc",
"ambient/ambient_controller.h",
Expand Down
117 changes: 117 additions & 0 deletions ash/ambient/ambient_access_token_controller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2020 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 "ash/ambient/ambient_access_token_controller.h"

#include <algorithm>
#include <utility>

#include "ash/public/cpp/ambient/ambient_client.h"
#include "base/rand_util.h"
#include "base/time/time.h"

namespace ash {

namespace {

constexpr base::TimeDelta kMinTokenRefreshDelay =
base::TimeDelta::FromMilliseconds(1000);
constexpr base::TimeDelta kMaxTokenRefreshDelay =
base::TimeDelta::FromMilliseconds(60 * 1000);

// The buffer time to use the access token.
constexpr base::TimeDelta kTokenExpirationTimeBuffer =
base::TimeDelta::FromMinutes(10);

} // namespace

AmbientAccessTokenController::AmbientAccessTokenController() = default;

AmbientAccessTokenController::~AmbientAccessTokenController() = default;

void AmbientAccessTokenController::RequestAccessToken(
AccessTokenCallback callback) {
// |token_refresh_timer_| may become stale during sleeping.
if (token_refresh_timer_.IsRunning())
token_refresh_timer_.AbandonAndStop();

if (!access_token_.empty()) {
DCHECK(!has_pending_request_);

// Return the token if there is enough time to use the access token when
// requested.
if (expiration_time_ - base::Time::Now() > kTokenExpirationTimeBuffer) {
RunCallback(std::move(callback));
return;
}

access_token_ = std::string();
expiration_time_ = base::Time::Now();
}

callbacks_.emplace_back(std::move(callback));

if (has_pending_request_)
return;

RefreshAccessToken();
}

void AmbientAccessTokenController::RefreshAccessToken() {
DCHECK(!token_refresh_timer_.IsRunning());

has_pending_request_ = true;
AmbientClient::Get()->RequestAccessToken(
base::BindOnce(&AmbientAccessTokenController::AccessTokenRefreshed,
weak_factory_.GetWeakPtr()));
}

void AmbientAccessTokenController::AccessTokenRefreshed(
const std::string& gaia_id,
const std::string& access_token,
const base::Time& expiration_time) {
has_pending_request_ = false;

if (gaia_id.empty() || access_token.empty()) {
RetryRefreshAccessToken();
return;
}

VLOG(1) << "Access token fetched.";
gaia_id_ = gaia_id;
access_token_ = access_token;
expiration_time_ = expiration_time;
NotifyAccessTokenRefreshed();
}

void AmbientAccessTokenController::RetryRefreshAccessToken() {
base::TimeDelta backoff_delay =
std::min(kMinTokenRefreshDelay *
(1 << (token_refresh_error_backoff_factor - 1)),
kMaxTokenRefreshDelay) +
base::RandDouble() * kMinTokenRefreshDelay;

if (backoff_delay < kMaxTokenRefreshDelay)
++token_refresh_error_backoff_factor;

token_refresh_timer_.Start(
FROM_HERE, backoff_delay,
base::BindOnce(&AmbientAccessTokenController::RefreshAccessToken,
base::Unretained(this)));
}

void AmbientAccessTokenController::NotifyAccessTokenRefreshed() {
for (auto& callback : callbacks_)
RunCallback(std::move(callback));

callbacks_.clear();
}

void AmbientAccessTokenController::RunCallback(AccessTokenCallback callback) {
DCHECK(!gaia_id_.empty());
DCHECK(!access_token_.empty());
std::move(callback).Run(gaia_id_, access_token_);
}

} // namespace ash
65 changes: 65 additions & 0 deletions ash/ambient/ambient_access_token_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2020 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 ASH_AMBIENT_AMBIENT_ACCESS_TOKEN_CONTROLLER_H_
#define ASH_AMBIENT_AMBIENT_ACCESS_TOKEN_CONTROLLER_H_

#include <string>
#include <vector>

#include "ash/ash_export.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"

namespace ash {

// A class to manage the access token for ambient mode. Request will be async
// and will be returned as soon as the token is refreshed. If the token has
// already been refreshed, request call will be returned immediately.
class ASH_EXPORT AmbientAccessTokenController {
public:
using AccessTokenCallback =
base::OnceCallback<void(const std::string& gaia_id,
const std::string& access_token)>;

AmbientAccessTokenController();
AmbientAccessTokenController(const AmbientAccessTokenController&) = delete;
AmbientAccessTokenController& operator=(const AmbientAccessTokenController&) =
delete;
~AmbientAccessTokenController();

void RequestAccessToken(AccessTokenCallback callback);

private:
friend class AmbientAshTestBase;

void RefreshAccessToken();
void AccessTokenRefreshed(const std::string& gaia_id,
const std::string& access_token,
const base::Time& expiration_time);
void RetryRefreshAccessToken();
void NotifyAccessTokenRefreshed();
void RunCallback(AccessTokenCallback callback);

std::string gaia_id_;
std::string access_token_;

// The expiration time of the |access_token_|.
base::Time expiration_time_;

// True if has already sent access token request and waiting for result.
bool has_pending_request_ = false;

base::OneShotTimer token_refresh_timer_;
int token_refresh_error_backoff_factor = 1;

std::vector<AccessTokenCallback> callbacks_;

base::WeakPtrFactory<AmbientAccessTokenController> weak_factory_{this};
};

} // namespace ash

#endif // ASH_AMBIENT_AMBIENT_ACCESS_TOKEN_CONTROLLER_H_
28 changes: 28 additions & 0 deletions ash/ambient/ambient_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "ash/ambient/ambient_controller.h"

#include <string>
#include <utility>

#include "ash/ambient/ambient_constants.h"
#include "ash/ambient/fake_ambient_backend_controller_impl.h"
Expand All @@ -19,6 +20,7 @@
#include "ash/public/cpp/assistant/controller/assistant_ui_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/bind_helpers.h"
#include "build/buildflag.h"
#include "chromeos/assistant/buildflags.h"
#include "chromeos/constants/chromeos_features.h"
Expand Down Expand Up @@ -111,6 +113,27 @@ void AmbientController::OnLockStateChanged(bool locked) {
if (locked) {
// Show ambient mode when entering lock screen.
DCHECK(!container_view_);

// We have 3 options to manage the token for lock screen. Here use option 1.
// 1. Request only one time after entering lock screen. We will use it once
// to request all the image links and no more requests.
// 2. Request one time before entering lock screen. This will introduce
// extra latency.
// 3. Request and refresh the token in the background (even the ambient mode
// is not started) with extra buffer time to use. When entering
// lock screen, it will be most likely to have the token already and
// enough time to use. More specifically,
// 3a. We will leave enough buffer time (e.g. 10 mins before expire) to
// start to refresh the token.
// 3b. When lock screen is triggered, most likely we will have >10 mins
// of token which can be used on lock screen.
// 3c. There is a corner case that we may not have the token fetched when
// locking screen, we probably can use PrepareForLock(callback) when
// locking screen. We can add the refresh token into it. If the token
// has already been fetched, then there is not additional time to
// wait.
RequestAccessToken(base::DoNothing());

Show();
} else {
// Destroy ambient mode after user re-login.
Expand Down Expand Up @@ -163,6 +186,11 @@ void AmbientController::StartFadeOutAnimation() {
container_view_->FadeOutPhotoView();
}

void AmbientController::RequestAccessToken(
AmbientAccessTokenController::AccessTokenCallback callback) {
access_token_controller_.RequestAccessToken(std::move(callback));
}

void AmbientController::CreateContainerView() {
DCHECK(!container_view_);
container_view_ = new AmbientContainerView(&delegate_);
Expand Down
7 changes: 7 additions & 0 deletions ash/ambient/ambient_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#ifndef ASH_AMBIENT_AMBIENT_CONTROLLER_H_
#define ASH_AMBIENT_AMBIENT_CONTROLLER_H_

#include <memory>

#include "ash/ambient/ambient_access_token_controller.h"
#include "ash/ambient/ambient_photo_controller.h"
#include "ash/ambient/ambient_view_delegate_impl.h"
#include "ash/ambient/model/ambient_backend_model.h"
Expand Down Expand Up @@ -56,6 +59,9 @@ class ASH_EXPORT AmbientController : public views::WidgetObserver,
// Should be removed once we delete the shortcut entry point.
void Toggle();

void RequestAccessToken(
AmbientAccessTokenController::AccessTokenCallback callback);

AmbientBackendModel* ambient_backend_model() {
return &ambient_backend_model_;
}
Expand Down Expand Up @@ -108,6 +114,7 @@ class ASH_EXPORT AmbientController : public views::WidgetObserver,
AmbientContainerView* container_view_ = nullptr; // Owned by view hierarchy.
AmbientBackendModel ambient_backend_model_;
AmbientModeState ambient_state_;
AmbientAccessTokenController access_token_controller_;
std::unique_ptr<AmbientBackendController> ambient_backend_controller_;
AmbientPhotoController ambient_photo_controller_;
base::OneShotTimer refresh_timer_;
Expand Down
68 changes: 68 additions & 0 deletions ash/ambient/ambient_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@

#include "ash/ambient/ambient_controller.h"

#include <string>
#include <utility>

#include "ash/ambient/test/ambient_ash_test_base.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"

namespace ash {

namespace {

constexpr base::TimeDelta kDefaultTokenExpirationDelay =
base::TimeDelta::FromHours(1);

} // namespace

using AmbientControllerTest = AmbientAshTestBase;

TEST_F(AmbientControllerTest, ShowAmbientContainerViewOnLockScreen) {
Expand All @@ -17,4 +29,60 @@ TEST_F(AmbientControllerTest, ShowAmbientContainerViewOnLockScreen) {
EXPECT_TRUE(ambient_controller()->is_showing());
}

TEST_F(AmbientControllerTest, ShouldRequestAccessTokenWhenLockingScreen) {
EXPECT_FALSE(IsAccessTokenRequestPending());

// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
std::string access_token = "access_token";
IssueAccessToken(access_token, /*with_error=*/false);
EXPECT_FALSE(IsAccessTokenRequestPending());

// Should close ambient widget already when unlocking screen.
ambient_controller()->Toggle();
UnlockScreen();
EXPECT_FALSE(IsAccessTokenRequestPending());
}

TEST_F(AmbientControllerTest, ShouldReturnCachedAccessToken) {
EXPECT_FALSE(IsAccessTokenRequestPending());

// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
std::string access_token = "access_token";
IssueAccessToken(access_token, /*with_error=*/false);
EXPECT_FALSE(IsAccessTokenRequestPending());

// Another token request will return cached token.
base::OnceClosure closure = base::MakeExpectedRunClosure(FROM_HERE);
base::RunLoop run_loop;
ambient_controller()->RequestAccessToken(base::BindLambdaForTesting(
[&](const std::string& gaia_id, const std::string& access_token_fetched) {
EXPECT_EQ(access_token_fetched, access_token);

std::move(closure).Run();
run_loop.Quit();
}));
EXPECT_FALSE(IsAccessTokenRequestPending());
run_loop.Run();
}

TEST_F(AmbientControllerTest, ShouldRefreshAccessTokenAfterFailure) {
EXPECT_FALSE(IsAccessTokenRequestPending());

// Lock the screen will request a token.
LockScreen();
EXPECT_TRUE(IsAccessTokenRequestPending());
IssueAccessToken(/*access_token=*/std::string(), /*with_error=*/true);
EXPECT_FALSE(IsAccessTokenRequestPending());

// Token request automatically retry.
// The failure delay has jitter so fast forward a bit more, but before
// the returned token would expire again.
task_environment()->FastForwardBy(kDefaultTokenExpirationDelay / 2);
EXPECT_TRUE(IsAccessTokenRequestPending());
}

} // namespace ash
2 changes: 1 addition & 1 deletion ash/ambient/ambient_photo_controller_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
#include "ash/ambient/ambient_photo_controller.h"

#include <memory>
#include <utility>

#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/fake_ambient_backend_controller_impl.h"
#include "ash/ambient/test/ambient_ash_test_base.h"
#include "ash/public/cpp/ambient/ambient_backend_controller.h"
#include "ash/shell.h"
Expand Down
Loading

0 comments on commit 942fe1f

Please sign in to comment.