1 /*
<lambda>null2  * Copyright (C) 2024 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 
17 package com.android.systemui.communal.data.backup
18 
19 import androidx.room.Room
20 import androidx.test.ext.junit.runners.AndroidJUnit4
21 import androidx.test.filters.SmallTest
22 import com.android.systemui.SysuiTestCase
23 import com.android.systemui.communal.data.db.CommunalDatabase
24 import com.android.systemui.communal.data.db.CommunalWidgetDao
25 import com.android.systemui.communal.nano.CommunalHubState
26 import com.android.systemui.communal.shared.model.SpanValue
27 import com.android.systemui.communal.shared.model.toFixed
28 import com.android.systemui.communal.shared.model.toResponsive
29 import com.android.systemui.lifecycle.InstantTaskExecutorRule
30 import com.google.common.truth.Correspondence
31 import com.google.common.truth.Truth.assertThat
32 import java.io.FileNotFoundException
33 import java.nio.charset.Charset
34 import org.junit.After
35 import org.junit.Before
36 import org.junit.Rule
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 
40 @SmallTest
41 @RunWith(AndroidJUnit4::class)
42 class CommunalBackupUtilsTest : SysuiTestCase() {
43     @JvmField @Rule val instantTaskExecutor = InstantTaskExecutorRule()
44 
45     private lateinit var database: CommunalDatabase
46     private lateinit var dao: CommunalWidgetDao
47     private lateinit var underTest: CommunalBackupUtils
48 
49     @Before
50     fun setup() {
51         database =
52             Room.inMemoryDatabaseBuilder(context, CommunalDatabase::class.java)
53                 .allowMainThreadQueries()
54                 .build()
55         CommunalDatabase.setInstance(database)
56 
57         dao = database.communalWidgetDao()
58         underTest = CommunalBackupUtils(context)
59     }
60 
61     @After
62     fun teardown() {
63         database.close()
64         underTest.clear()
65     }
66 
67     @Test
68     fun getCommunalHubState_returnsExpectedWidgets() {
69         // Set up database
70         val expectedWidgets =
71             listOf(
72                 FakeWidgetMetadata(
73                     widgetId = 11,
74                     componentName = "com.android.fakePackage1/fakeWidget1",
75                     rank = 0,
76                     userSerialNumber = 0,
77                     spanY = SpanValue.Responsive(1),
78                 ),
79                 FakeWidgetMetadata(
80                     widgetId = 12,
81                     componentName = "com.android.fakePackage2/fakeWidget2",
82                     rank = 1,
83                     userSerialNumber = 0,
84                     spanY = SpanValue.Responsive(2),
85                 ),
86                 FakeWidgetMetadata(
87                     widgetId = 13,
88                     componentName = "com.android.fakePackage3/fakeWidget3",
89                     rank = 2,
90                     userSerialNumber = 10,
91                     spanY = SpanValue.Responsive(3),
92                 ),
93             )
94         expectedWidgets.forEach {
95             dao.addWidget(it.widgetId, it.componentName, it.rank, it.userSerialNumber, it.spanY)
96         }
97 
98         // Get communal hub state
99         val state = underTest.getCommunalHubState()
100         val actualWidgets = state.widgets.toList()
101 
102         // Verify the state contains widgets as expected
103         assertThat(actualWidgets)
104             .comparingElementsUsing(represents)
105             .containsExactlyElementsIn(expectedWidgets)
106     }
107 
108     @Test
109     fun write_existingContentIsOverwritten() {
110         // Write old data
111         val dataToWrite = "I am old data. Erase me."
112         underTest.writeBytesToDisk(dataToWrite.toByteArray(Charset.defaultCharset()))
113 
114         // Verify old data written
115         var dataRead = underTest.readBytesFromDisk().toString(Charset.defaultCharset())
116         assertThat(dataRead).isEqualTo(dataToWrite)
117 
118         // Write new data
119         val newDataToWrite = "I am new data."
120         underTest.writeBytesToDisk(newDataToWrite.toByteArray(Charset.defaultCharset()))
121 
122         // Verify new data overwrites old
123         dataRead = underTest.readBytesFromDisk().toString(Charset.defaultCharset())
124         assertThat(dataRead).isEqualTo(newDataToWrite)
125     }
126 
127     @Test(expected = FileNotFoundException::class)
128     fun clear_returnsTrueWhenFileDeleted() {
129         // Write bytes to disk
130         underTest.writeBytesToDisk(byteArrayOf(1, 2, 3))
131 
132         assertThat(underTest.clear()).isTrue()
133 
134         // Verify a read after that throws a FileNotFoundException
135         underTest.readBytesFromDisk()
136     }
137 
138     @Test
139     fun clear_returnsFalseWhenFileDoesNotExist() {
140         assertThat(underTest.clear()).isFalse()
141     }
142 
143     @Test
144     fun fileExists() {
145         assertThat(underTest.fileExists()).isFalse()
146 
147         underTest.writeBytesToDisk(byteArrayOf(1, 2, 3))
148         assertThat(underTest.fileExists()).isTrue()
149 
150         underTest.clear()
151         assertThat(underTest.fileExists()).isFalse()
152     }
153 
154     data class FakeWidgetMetadata(
155         val widgetId: Int,
156         val componentName: String,
157         val rank: Int,
158         val userSerialNumber: Int,
159         val spanY: SpanValue,
160     )
161 
162     companion object {
163         /**
164          * A comparator for whether a [CommunalHubState.CommunalWidgetItem] represents a
165          * [FakeWidgetMetadata]
166          */
167         val represents: Correspondence<CommunalHubState.CommunalWidgetItem, FakeWidgetMetadata> =
168             Correspondence.from(
169                 { actual, expected ->
170                     actual?.widgetId == expected?.widgetId &&
171                         actual?.componentName == expected?.componentName &&
172                         actual?.rank == expected?.rank &&
173                         actual?.userSerialNumber == expected?.userSerialNumber &&
174                         actual?.spanY == expected?.spanY?.toFixed()?.value &&
175                         actual?.spanYNew == expected?.spanY?.toResponsive()?.value
176                 },
177                 "represents",
178             )
179     }
180 }
181