blob: 4c0a868f0dbda2735be4ce2e3a7ce78bb927f5d6 [file] [log] [blame]
Tianjie Xu5fe5eb62018-03-20 16:07:39 -07001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Tao Bao1fe1afe2018-05-01 15:56:05 -070017#include <stddef.h>
Tao Bao152e0eb2018-05-13 00:34:45 -070018#include <stdio.h>
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070019
Tao Bao6cd81682018-05-03 21:53:11 -070020#include <functional>
21#include <map>
22#include <memory>
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070023#include <string>
Tao Bao1fe1afe2018-05-01 15:56:05 -070024#include <vector>
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070025
Tao Bao6cd81682018-05-03 21:53:11 -070026#include <android-base/logging.h>
Tao Bao152e0eb2018-05-13 00:34:45 -070027#include <android-base/stringprintf.h>
28#include <android-base/test_utils.h>
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070029#include <gtest/gtest.h>
30
Tao Bao6cd81682018-05-03 21:53:11 -070031#include "common/test_constants.h"
32#include "device.h"
Tao Bao51f16ec2018-06-04 11:40:20 -070033#include "minui/minui.h"
Tao Bao6cd81682018-05-03 21:53:11 -070034#include "otautil/paths.h"
35#include "private/resources.h"
Tao Bao1fe1afe2018-05-01 15:56:05 -070036#include "screen_ui.h"
37
38static const std::vector<std::string> HEADERS{ "header" };
39static const std::vector<std::string> ITEMS{ "item1", "item2", "item3", "item4", "1234567890" };
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070040
41TEST(ScreenUITest, StartPhoneMenuSmoke) {
Tao Bao1fe1afe2018-05-01 15:56:05 -070042 Menu menu(false, 10, 20, HEADERS, ITEMS, 0);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070043 ASSERT_FALSE(menu.scrollable());
Tao Bao1fe1afe2018-05-01 15:56:05 -070044 ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070045 ASSERT_EQ(5u, menu.ItemsCount());
46
47 std::string message;
48 ASSERT_FALSE(menu.ItemsOverflow(&message));
49 for (size_t i = 0; i < menu.ItemsCount(); i++) {
50 ASSERT_EQ(ITEMS[i], menu.TextItem(i));
51 }
52
53 ASSERT_EQ(0, menu.selection());
54}
55
56TEST(ScreenUITest, StartWearMenuSmoke) {
Tao Bao1fe1afe2018-05-01 15:56:05 -070057 Menu menu(true, 10, 8, HEADERS, ITEMS, 1);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070058 ASSERT_TRUE(menu.scrollable());
Tao Bao1fe1afe2018-05-01 15:56:05 -070059 ASSERT_EQ(HEADERS[0], menu.text_headers()[0]);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070060 ASSERT_EQ(5u, menu.ItemsCount());
61
62 std::string message;
63 ASSERT_FALSE(menu.ItemsOverflow(&message));
64 for (size_t i = 0; i < menu.ItemsCount() - 1; i++) {
65 ASSERT_EQ(ITEMS[i], menu.TextItem(i));
66 }
67 // Test of the last item is truncated
68 ASSERT_EQ("12345678", menu.TextItem(4));
69 ASSERT_EQ(1, menu.selection());
70}
71
72TEST(ScreenUITest, StartPhoneMenuItemsOverflow) {
Tao Bao1fe1afe2018-05-01 15:56:05 -070073 Menu menu(false, 1, 20, HEADERS, ITEMS, 0);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070074 ASSERT_FALSE(menu.scrollable());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070075 ASSERT_EQ(1u, menu.ItemsCount());
76
77 std::string message;
78 ASSERT_FALSE(menu.ItemsOverflow(&message));
79 for (size_t i = 0; i < menu.ItemsCount(); i++) {
80 ASSERT_EQ(ITEMS[i], menu.TextItem(i));
81 }
82
83 ASSERT_EQ(0u, menu.MenuStart());
84 ASSERT_EQ(1u, menu.MenuEnd());
85}
86
87TEST(ScreenUITest, StartWearMenuItemsOverflow) {
Tao Bao1fe1afe2018-05-01 15:56:05 -070088 Menu menu(true, 1, 20, HEADERS, ITEMS, 0);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070089 ASSERT_TRUE(menu.scrollable());
Tianjie Xu5fe5eb62018-03-20 16:07:39 -070090 ASSERT_EQ(5u, menu.ItemsCount());
91
92 std::string message;
93 ASSERT_TRUE(menu.ItemsOverflow(&message));
94 ASSERT_EQ("Current item: 1/5", message);
95
96 for (size_t i = 0; i < menu.ItemsCount(); i++) {
97 ASSERT_EQ(ITEMS[i], menu.TextItem(i));
98 }
99
100 ASSERT_EQ(0u, menu.MenuStart());
101 ASSERT_EQ(1u, menu.MenuEnd());
102}
103
104TEST(ScreenUITest, PhoneMenuSelectSmoke) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700105 int sel = 0;
Tao Bao1fe1afe2018-05-01 15:56:05 -0700106 Menu menu(false, 10, 20, HEADERS, ITEMS, sel);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700107 // Mimic down button 10 times (2 * items size)
108 for (int i = 0; i < 10; i++) {
109 sel = menu.Select(++sel);
110 ASSERT_EQ(sel, menu.selection());
111
112 // Wraps the selection for unscrollable menu when it reaches the boundary.
113 int expected = (i + 1) % 5;
114 ASSERT_EQ(expected, menu.selection());
115
116 ASSERT_EQ(0u, menu.MenuStart());
117 ASSERT_EQ(5u, menu.MenuEnd());
118 }
119
120 // Mimic up button 10 times
121 for (int i = 0; i < 10; i++) {
122 sel = menu.Select(--sel);
123 ASSERT_EQ(sel, menu.selection());
124
125 int expected = (9 - i) % 5;
126 ASSERT_EQ(expected, menu.selection());
127
128 ASSERT_EQ(0u, menu.MenuStart());
129 ASSERT_EQ(5u, menu.MenuEnd());
130 }
131}
132
133TEST(ScreenUITest, WearMenuSelectSmoke) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700134 int sel = 0;
Tao Bao1fe1afe2018-05-01 15:56:05 -0700135 Menu menu(true, 10, 20, HEADERS, ITEMS, sel);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700136 // Mimic pressing down button 10 times (2 * items size)
137 for (int i = 0; i < 10; i++) {
138 sel = menu.Select(++sel);
139 ASSERT_EQ(sel, menu.selection());
140
141 // Stops the selection at the boundary if the menu is scrollable.
142 int expected = std::min(i + 1, 4);
143 ASSERT_EQ(expected, menu.selection());
144
145 ASSERT_EQ(0u, menu.MenuStart());
146 ASSERT_EQ(5u, menu.MenuEnd());
147 }
148
149 // Mimic pressing up button 10 times
150 for (int i = 0; i < 10; i++) {
151 sel = menu.Select(--sel);
152 ASSERT_EQ(sel, menu.selection());
153
154 int expected = std::max(3 - i, 0);
155 ASSERT_EQ(expected, menu.selection());
156
157 ASSERT_EQ(0u, menu.MenuStart());
158 ASSERT_EQ(5u, menu.MenuEnd());
159 }
160}
161
162TEST(ScreenUITest, WearMenuSelectItemsOverflow) {
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700163 int sel = 1;
Tao Bao1fe1afe2018-05-01 15:56:05 -0700164 Menu menu(true, 3, 20, HEADERS, ITEMS, sel);
Tianjie Xu5fe5eb62018-03-20 16:07:39 -0700165 ASSERT_EQ(5u, menu.ItemsCount());
166
167 // Scroll the menu to the end, and check the start & end of menu.
168 for (int i = 0; i < 3; i++) {
169 sel = menu.Select(++sel);
170 ASSERT_EQ(i + 2, sel);
171 ASSERT_EQ(static_cast<size_t>(i), menu.MenuStart());
172 ASSERT_EQ(static_cast<size_t>(i + 3), menu.MenuEnd());
173 }
174
175 // Press down button one more time won't change the MenuStart() and MenuEnd().
176 sel = menu.Select(++sel);
177 ASSERT_EQ(4, sel);
178 ASSERT_EQ(2u, menu.MenuStart());
179 ASSERT_EQ(5u, menu.MenuEnd());
180
181 // Scroll the menu to the top.
182 // The expected menu sel, start & ends are:
183 // sel 3, start 2, end 5
184 // sel 2, start 2, end 5
185 // sel 1, start 1, end 4
186 // sel 0, start 0, end 3
187 for (int i = 0; i < 4; i++) {
188 sel = menu.Select(--sel);
189 ASSERT_EQ(3 - i, sel);
190 ASSERT_EQ(static_cast<size_t>(std::min(3 - i, 2)), menu.MenuStart());
191 ASSERT_EQ(static_cast<size_t>(std::min(6 - i, 5)), menu.MenuEnd());
192 }
193
194 // Press up button one more time won't change the MenuStart() and MenuEnd().
195 sel = menu.Select(--sel);
196 ASSERT_EQ(0, sel);
197 ASSERT_EQ(0u, menu.MenuStart());
198 ASSERT_EQ(3u, menu.MenuEnd());
199}
Tao Bao6cd81682018-05-03 21:53:11 -0700200
201static constexpr int kMagicAction = 101;
202
203enum class KeyCode : int {
204 TIMEOUT = -1,
205 NO_OP = 0,
206 UP = 1,
207 DOWN = 2,
208 ENTER = 3,
209 MAGIC = 1001,
210 LAST,
211};
212
213static const std::map<KeyCode, int> kKeyMapping{
214 // clang-format off
215 { KeyCode::NO_OP, Device::kNoAction },
216 { KeyCode::UP, Device::kHighlightUp },
217 { KeyCode::DOWN, Device::kHighlightDown },
218 { KeyCode::ENTER, Device::kInvokeItem },
219 { KeyCode::MAGIC, kMagicAction },
220 // clang-format on
221};
222
223class TestableScreenRecoveryUI : public ScreenRecoveryUI {
224 public:
225 int WaitKey() override;
226
227 void SetKeyBuffer(const std::vector<KeyCode>& buffer);
228
229 int KeyHandler(int key, bool visible) const;
230
Tao Bao152e0eb2018-05-13 00:34:45 -0700231 // The following functions expose the protected members for test purpose.
232 void RunLoadAnimation() {
233 LoadAnimation();
234 }
235
236 size_t GetLoopFrames() const {
237 return loop_frames;
238 }
239
240 size_t GetIntroFrames() const {
241 return intro_frames;
242 }
243
Tao Bao6cd81682018-05-03 21:53:11 -0700244 bool GetRtlLocale() const {
245 return rtl_locale_;
246 }
247
248 private:
249 std::vector<KeyCode> key_buffer_;
250 size_t key_buffer_index_;
251};
252
253void TestableScreenRecoveryUI::SetKeyBuffer(const std::vector<KeyCode>& buffer) {
254 key_buffer_ = buffer;
255 key_buffer_index_ = 0;
256}
257
258int TestableScreenRecoveryUI::KeyHandler(int key, bool) const {
259 KeyCode key_code = static_cast<KeyCode>(key);
260 if (kKeyMapping.find(key_code) != kKeyMapping.end()) {
261 return kKeyMapping.at(key_code);
262 }
263 return Device::kNoAction;
264}
265
266int TestableScreenRecoveryUI::WaitKey() {
267 CHECK_LT(key_buffer_index_, key_buffer_.size());
268 return static_cast<int>(key_buffer_[key_buffer_index_++]);
269}
270
271class ScreenRecoveryUITest : public ::testing::Test {
272 protected:
273 const std::string kTestLocale = "en-US";
274 const std::string kTestRtlLocale = "ar";
Tao Bao347a6592018-05-08 15:58:29 -0700275 const std::string kTestRtlLocaleWithSuffix = "ar-EG";
Tao Bao6cd81682018-05-03 21:53:11 -0700276
277 void SetUp() override {
Tao Bao51f16ec2018-06-04 11:40:20 -0700278 has_graphics_ = gr_init() == 0;
279 gr_exit();
280
281 if (has_graphics_) {
282 ui_ = std::make_unique<TestableScreenRecoveryUI>();
283 }
Tao Bao6cd81682018-05-03 21:53:11 -0700284
Tao Bao152e0eb2018-05-13 00:34:45 -0700285 testdata_dir_ = from_testdata_base("");
286 Paths::Get().set_resource_dir(testdata_dir_);
287 res_set_resource_dir(testdata_dir_);
Tao Bao6cd81682018-05-03 21:53:11 -0700288 }
289
Tao Bao51f16ec2018-06-04 11:40:20 -0700290 bool has_graphics_;
Tao Bao6cd81682018-05-03 21:53:11 -0700291 std::unique_ptr<TestableScreenRecoveryUI> ui_;
Tao Bao152e0eb2018-05-13 00:34:45 -0700292 std::string testdata_dir_;
Tao Bao6cd81682018-05-03 21:53:11 -0700293};
294
Tao Bao51f16ec2018-06-04 11:40:20 -0700295#define RETURN_IF_NO_GRAPHICS \
296 do { \
297 if (!has_graphics_) { \
298 GTEST_LOG_(INFO) << "Test skipped due to no available graphics device"; \
299 return; \
300 } \
301 } while (false)
302
Tao Bao6cd81682018-05-03 21:53:11 -0700303TEST_F(ScreenRecoveryUITest, Init) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700304 RETURN_IF_NO_GRAPHICS;
305
Tao Bao26ea9592018-05-09 16:32:02 -0700306 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700307 ASSERT_EQ(kTestLocale, ui_->GetLocale());
308 ASSERT_FALSE(ui_->GetRtlLocale());
309 ASSERT_FALSE(ui_->IsTextVisible());
310 ASSERT_FALSE(ui_->WasTextEverVisible());
311}
312
Tao Bao94371fd2018-06-06 07:38:54 -0700313TEST_F(ScreenRecoveryUITest, dtor_NotCallingInit) {
314 ui_.reset();
315 ASSERT_FALSE(ui_);
316}
317
Tao Bao6cd81682018-05-03 21:53:11 -0700318TEST_F(ScreenRecoveryUITest, ShowText) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700319 RETURN_IF_NO_GRAPHICS;
320
Tao Bao26ea9592018-05-09 16:32:02 -0700321 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700322 ASSERT_FALSE(ui_->IsTextVisible());
323 ui_->ShowText(true);
324 ASSERT_TRUE(ui_->IsTextVisible());
325 ASSERT_TRUE(ui_->WasTextEverVisible());
326
327 ui_->ShowText(false);
328 ASSERT_FALSE(ui_->IsTextVisible());
329 ASSERT_TRUE(ui_->WasTextEverVisible());
330}
331
332TEST_F(ScreenRecoveryUITest, RtlLocale) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700333 RETURN_IF_NO_GRAPHICS;
334
Tao Bao6cd81682018-05-03 21:53:11 -0700335 ASSERT_TRUE(ui_->Init(kTestRtlLocale));
336 ASSERT_TRUE(ui_->GetRtlLocale());
Tao Bao26ea9592018-05-09 16:32:02 -0700337}
Tao Bao6cd81682018-05-03 21:53:11 -0700338
Tao Bao26ea9592018-05-09 16:32:02 -0700339TEST_F(ScreenRecoveryUITest, RtlLocaleWithSuffix) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700340 RETURN_IF_NO_GRAPHICS;
341
Tao Bao6cd81682018-05-03 21:53:11 -0700342 ASSERT_TRUE(ui_->Init(kTestRtlLocaleWithSuffix));
343 ASSERT_TRUE(ui_->GetRtlLocale());
344}
345
346TEST_F(ScreenRecoveryUITest, ShowMenu) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700347 RETURN_IF_NO_GRAPHICS;
348
Tao Bao26ea9592018-05-09 16:32:02 -0700349 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700350 ui_->SetKeyBuffer({
351 KeyCode::UP,
352 KeyCode::DOWN,
353 KeyCode::UP,
354 KeyCode::DOWN,
355 KeyCode::ENTER,
356 });
357 ASSERT_EQ(3u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
358 std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
359 std::placeholders::_1, std::placeholders::_2)));
360
361 ui_->SetKeyBuffer({
362 KeyCode::UP,
363 KeyCode::UP,
364 KeyCode::NO_OP,
365 KeyCode::NO_OP,
366 KeyCode::UP,
367 KeyCode::ENTER,
368 });
369 ASSERT_EQ(2u, ui_->ShowMenu(HEADERS, ITEMS, 0, true,
370 std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
371 std::placeholders::_1, std::placeholders::_2)));
372}
373
374TEST_F(ScreenRecoveryUITest, ShowMenu_NotMenuOnly) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700375 RETURN_IF_NO_GRAPHICS;
376
Tao Bao26ea9592018-05-09 16:32:02 -0700377 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700378 ui_->SetKeyBuffer({
379 KeyCode::MAGIC,
380 });
381 ASSERT_EQ(static_cast<size_t>(kMagicAction),
382 ui_->ShowMenu(HEADERS, ITEMS, 3, false,
383 std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
384 std::placeholders::_1, std::placeholders::_2)));
385}
386
387TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700388 RETURN_IF_NO_GRAPHICS;
389
Tao Bao26ea9592018-05-09 16:32:02 -0700390 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700391 ui_->SetKeyBuffer({
392 KeyCode::TIMEOUT,
393 });
394 ASSERT_EQ(static_cast<size_t>(-1), ui_->ShowMenu(HEADERS, ITEMS, 3, true, nullptr));
395}
396
397TEST_F(ScreenRecoveryUITest, ShowMenu_TimedOut_TextWasEverVisible) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700398 RETURN_IF_NO_GRAPHICS;
399
Tao Bao26ea9592018-05-09 16:32:02 -0700400 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao6cd81682018-05-03 21:53:11 -0700401 ui_->ShowText(true);
402 ui_->ShowText(false);
403 ASSERT_TRUE(ui_->WasTextEverVisible());
404
405 ui_->SetKeyBuffer({
406 KeyCode::TIMEOUT,
407 KeyCode::DOWN,
408 KeyCode::ENTER,
409 });
410 ASSERT_EQ(4u, ui_->ShowMenu(HEADERS, ITEMS, 3, true,
411 std::bind(&TestableScreenRecoveryUI::KeyHandler, ui_.get(),
412 std::placeholders::_1, std::placeholders::_2)));
413}
Tao Bao152e0eb2018-05-13 00:34:45 -0700414
415TEST_F(ScreenRecoveryUITest, LoadAnimation) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700416 RETURN_IF_NO_GRAPHICS;
417
Tao Bao26ea9592018-05-09 16:32:02 -0700418 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Bao152e0eb2018-05-13 00:34:45 -0700419 // Make a few copies of loop00000.png from testdata.
420 std::string image_data;
421 ASSERT_TRUE(android::base::ReadFileToString(testdata_dir_ + "/loop00000.png", &image_data));
422
423 std::vector<std::string> tempfiles;
424 TemporaryDir resource_dir;
425 for (const auto& name : { "00002", "00100", "00050" }) {
426 tempfiles.push_back(android::base::StringPrintf("%s/loop%s.png", resource_dir.path, name));
427 ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
428 }
429 for (const auto& name : { "00", "01" }) {
430 tempfiles.push_back(android::base::StringPrintf("%s/intro%s.png", resource_dir.path, name));
431 ASSERT_TRUE(android::base::WriteStringToFile(image_data, tempfiles.back()));
432 }
433 Paths::Get().set_resource_dir(resource_dir.path);
434
435 ui_->RunLoadAnimation();
436
437 ASSERT_EQ(2u, ui_->GetIntroFrames());
438 ASSERT_EQ(3u, ui_->GetLoopFrames());
439
440 for (const auto& name : tempfiles) {
441 ASSERT_EQ(0, unlink(name.c_str()));
442 }
443}
444
445TEST_F(ScreenRecoveryUITest, LoadAnimation_MissingAnimation) {
Tao Bao51f16ec2018-06-04 11:40:20 -0700446 RETURN_IF_NO_GRAPHICS;
447
Tao Bao26ea9592018-05-09 16:32:02 -0700448 ASSERT_TRUE(ui_->Init(kTestLocale));
Tao Baob1c5b622018-07-12 11:49:05 -0700449 // We need a dir that doesn't contain any animation. However, using TemporaryDir will give
450 // leftovers since this is a death test where TemporaryDir::~TemporaryDir() won't be called.
451 Paths::Get().set_resource_dir("/proc/self");
Tao Bao42be0d42018-06-05 14:03:34 -0700452
453 ::testing::FLAGS_gtest_death_test_style = "threadsafe";
Tao Bao152e0eb2018-05-13 00:34:45 -0700454 ASSERT_EXIT(ui_->RunLoadAnimation(), ::testing::KilledBySignal(SIGABRT), "");
455}
Tao Bao51f16ec2018-06-04 11:40:20 -0700456
457#undef RETURN_IF_NO_GRAPHICS