1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/test/test_session_state_animator.h"
6
7#include <vector>
8
9#include "base/bind.h"
10
11namespace ash {
12namespace test {
13
14namespace {
15// A no-op callback that can be used when managing an animation that didn't
16// actually have a callback given.
17void DummyCallback() {}
18}
19
20const SessionStateAnimator::Container
21    TestSessionStateAnimator::kAllContainers[] = {
22        SessionStateAnimator::DESKTOP_BACKGROUND,
23        SessionStateAnimator::LAUNCHER,
24        SessionStateAnimator::NON_LOCK_SCREEN_CONTAINERS,
25        SessionStateAnimator::LOCK_SCREEN_BACKGROUND,
26        SessionStateAnimator::LOCK_SCREEN_CONTAINERS,
27        SessionStateAnimator::LOCK_SCREEN_RELATED_CONTAINERS,
28        SessionStateAnimator::ROOT_CONTAINER
29    };
30
31// A simple SessionStateAnimator::AnimationSequence that tracks the number of
32// attached sequences.  The callback will be invoked if all animations complete
33// successfully.
34class TestSessionStateAnimator::AnimationSequence
35    : public SessionStateAnimator::AnimationSequence {
36 public:
37  AnimationSequence(base::Closure callback, TestSessionStateAnimator* animator)
38      : SessionStateAnimator::AnimationSequence(callback),
39        sequence_count_(0),
40        sequence_aborted_(false),
41        animator_(animator) {
42  }
43
44  virtual ~AnimationSequence() {}
45
46  virtual void SequenceAttached() {
47    ++sequence_count_;
48  }
49
50  // Notify the sequence that is has completed.
51  virtual void SequenceFinished(bool successfully) {
52    DCHECK_GT(sequence_count_, 0);
53    --sequence_count_;
54    sequence_aborted_ |= !successfully;
55    if (sequence_count_ == 0) {
56      if (sequence_aborted_)
57        OnAnimationAborted();
58      else
59        OnAnimationCompleted();
60    }
61  }
62
63  // ash::SessionStateAnimator::AnimationSequence:
64  virtual void StartAnimation(int container_mask,
65                              AnimationType type,
66                              AnimationSpeed speed) OVERRIDE {
67    animator_->StartAnimationInSequence(container_mask, type, speed, this);
68  }
69
70 private:
71  // Tracks the number of contained animations.
72  int sequence_count_;
73
74  // True if the sequence was aborted.
75  bool sequence_aborted_;
76
77  // The TestSessionAnimator that created this.  Not owned.
78  TestSessionStateAnimator* animator_;
79
80  DISALLOW_COPY_AND_ASSIGN(AnimationSequence);
81};
82
83TestSessionStateAnimator::ActiveAnimation::ActiveAnimation(
84    int animation_epoch,
85    base::TimeDelta duration,
86    SessionStateAnimator::Container container,
87    AnimationType type,
88    AnimationSpeed speed,
89    base::Closure success_callback,
90    base::Closure failed_callback)
91    : animation_epoch(animation_epoch),
92      remaining_duration(duration),
93      container(container),
94      type(type),
95      speed(speed),
96      success_callback(success_callback),
97      failed_callback(failed_callback) {
98}
99
100TestSessionStateAnimator::ActiveAnimation::~ActiveAnimation() {
101}
102
103TestSessionStateAnimator::TestSessionStateAnimator()
104    : last_animation_epoch_(0),
105      is_background_hidden_(false) {
106}
107
108TestSessionStateAnimator::~TestSessionStateAnimator() {
109  CompleteAllAnimations(false);
110}
111
112void TestSessionStateAnimator::ResetAnimationEpoch() {
113  CompleteAllAnimations(false);
114  last_animation_epoch_ = 0;
115}
116
117void TestSessionStateAnimator::Advance(const base::TimeDelta& duration) {
118  for (ActiveAnimationsMap::iterator container_iter =
119           active_animations_.begin();
120       container_iter != active_animations_.end();
121       ++container_iter) {
122    AnimationList::iterator animation_iter = (*container_iter).second.begin();
123    while (animation_iter != (*container_iter).second.end()) {
124      ActiveAnimation& active_animation = *animation_iter;
125      active_animation.remaining_duration -= duration;
126      if (active_animation.remaining_duration <= base::TimeDelta()) {
127        active_animation.success_callback.Run();
128        animation_iter = (*container_iter).second.erase(animation_iter);
129      } else {
130        ++animation_iter;
131      }
132    }
133  }
134}
135
136void TestSessionStateAnimator::CompleteAnimations(int animation_epoch,
137                                                  bool completed_successfully) {
138  for (ActiveAnimationsMap::iterator container_iter =
139           active_animations_.begin();
140       container_iter != active_animations_.end();
141       ++container_iter) {
142    AnimationList::iterator animation_iter = (*container_iter).second.begin();
143    while (animation_iter != (*container_iter).second.end()) {
144      ActiveAnimation active_animation = *animation_iter;
145      if (active_animation.animation_epoch <= animation_epoch) {
146        if (completed_successfully)
147          active_animation.success_callback.Run();
148        else
149          active_animation.failed_callback.Run();
150        animation_iter = (*container_iter).second.erase(animation_iter);
151      } else {
152        ++animation_iter;
153      }
154    }
155  }
156}
157
158void TestSessionStateAnimator::CompleteAllAnimations(
159    bool completed_successfully) {
160  CompleteAnimations(last_animation_epoch_, completed_successfully);
161}
162
163bool TestSessionStateAnimator::IsContainerAnimated(
164    SessionStateAnimator::Container container,
165    SessionStateAnimator::AnimationType type) const {
166  ActiveAnimationsMap::const_iterator container_iter =
167      active_animations_.find(container);
168  if (container_iter != active_animations_.end()) {
169    for (AnimationList::const_iterator animation_iter =
170          (*container_iter).second.begin();
171         animation_iter != (*container_iter).second.end();
172         ++animation_iter) {
173      const ActiveAnimation& active_animation = *animation_iter;
174      if (active_animation.type == type)
175        return true;
176    }
177  }
178  return false;
179}
180
181bool TestSessionStateAnimator::AreContainersAnimated(
182    int container_mask, SessionStateAnimator::AnimationType type) const {
183  for (size_t i = 0; i < arraysize(kAllContainers); ++i) {
184    if (container_mask & kAllContainers[i] &&
185        !IsContainerAnimated(kAllContainers[i], type)) {
186      return false;
187    }
188  }
189  return true;
190}
191
192size_t TestSessionStateAnimator::GetAnimationCount() const {
193  size_t count = 0;
194  for (ActiveAnimationsMap::const_iterator container_iter =
195          active_animations_.begin();
196       container_iter != active_animations_.end();
197       ++container_iter) {
198    count += (*container_iter).second.size();
199  }
200  return count;
201}
202
203void TestSessionStateAnimator::StartAnimation(int container_mask,
204                                              AnimationType type,
205                                              AnimationSpeed speed) {
206  ++last_animation_epoch_;
207  for (size_t i = 0; i < arraysize(kAllContainers); ++i) {
208    if (container_mask & kAllContainers[i]) {
209      // Use a dummy no-op callback because one isn't required by the client
210      // but one is required when completing or aborting animations.
211      base::Closure callback = base::Bind(&DummyCallback);
212      AddAnimation(kAllContainers[i], type, speed, callback, callback);
213    }
214  }
215}
216
217void TestSessionStateAnimator::StartAnimationWithCallback(
218    int container_mask,
219    AnimationType type,
220    AnimationSpeed speed,
221    base::Closure callback) {
222  ++last_animation_epoch_;
223  for (size_t i = 0; i < arraysize(kAllContainers); ++i)
224    if (container_mask & kAllContainers[i]) {
225      // ash::SessionStateAnimatorImpl invokes the callback whether or not the
226      // animation was completed successfully or not.
227      AddAnimation(kAllContainers[i], type, speed, callback, callback);
228    }
229}
230
231ash::SessionStateAnimator::AnimationSequence*
232    TestSessionStateAnimator::BeginAnimationSequence(base::Closure callback) {
233  return new AnimationSequence(callback, this);
234}
235
236bool TestSessionStateAnimator::IsBackgroundHidden() const {
237  return is_background_hidden_;
238}
239
240void TestSessionStateAnimator::ShowBackground() {
241  is_background_hidden_ = false;
242}
243
244void TestSessionStateAnimator::HideBackground() {
245  is_background_hidden_ = true;
246}
247
248void TestSessionStateAnimator::StartAnimationInSequence(
249    int container_mask,
250    AnimationType type,
251    AnimationSpeed speed,
252    AnimationSequence* animation_sequence) {
253  ++last_animation_epoch_;
254  for (size_t i = 0; i < arraysize(kAllContainers); ++i) {
255    if (container_mask & kAllContainers[i]) {
256      base::Closure success_callback =
257          base::Bind(&AnimationSequence::SequenceFinished,
258                     base::Unretained(animation_sequence), true);
259      base::Closure failed_callback =
260          base::Bind(&AnimationSequence::SequenceFinished,
261                     base::Unretained(animation_sequence), false);
262      animation_sequence->SequenceAttached();
263      AddAnimation(kAllContainers[i], type, speed, success_callback,
264          failed_callback);
265    }
266  }
267}
268
269void TestSessionStateAnimator::AddAnimation(
270    SessionStateAnimator::Container container,
271    AnimationType type,
272    AnimationSpeed speed,
273    base::Closure success_callback,
274    base::Closure failed_callback) {
275  base::TimeDelta duration = GetDuration(speed);
276  ActiveAnimation active_animation(last_animation_epoch_,
277                                   duration,
278                                   container,
279                                   type,
280                                   speed,
281                                   success_callback,
282                                   failed_callback);
283  // This test double is limited to only have one animation active for a given
284  // container at a time.
285  AbortAnimation(container);
286  active_animations_[container].push_back(active_animation);
287}
288
289void TestSessionStateAnimator::AbortAnimation(
290    SessionStateAnimator::Container container) {
291  ActiveAnimationsMap::iterator container_iter =
292      active_animations_.find(container);
293  if (container_iter != active_animations_.end()) {
294    AnimationList::iterator animation_iter = (*container_iter).second.begin();
295    while (animation_iter != (*container_iter).second.end()) {
296      ActiveAnimation active_animation = *animation_iter;
297      active_animation.failed_callback.Run();
298      animation_iter = (*container_iter).second.erase(animation_iter);
299    }
300  }
301}
302
303}  // namespace test
304}  // namespace ash
305