• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.launcher3.util
18 
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.pm.LauncherApps
22 import android.os.Bundle
23 import android.os.Process
24 import android.platform.test.flag.junit.SetFlagsRule
25 import android.view.View.OnClickListener
26 import android.view.View.OnFocusChangeListener
27 import android.widget.FrameLayout
28 import androidx.test.core.app.ApplicationProvider.getApplicationContext
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.filters.SmallTest
31 import androidx.test.platform.app.InstrumentationRegistry
32 import com.android.launcher3.BubbleTextView
33 import com.android.launcher3.Flags
34 import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
35 import com.android.launcher3.apppairs.AppPairIcon
36 import com.android.launcher3.folder.FolderIcon
37 import com.android.launcher3.model.ModelWriter
38 import com.android.launcher3.model.data.AppInfo
39 import com.android.launcher3.model.data.AppPairInfo
40 import com.android.launcher3.model.data.FolderInfo
41 import com.android.launcher3.model.data.LauncherAppWidgetInfo
42 import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
43 import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
44 import com.android.launcher3.model.data.LauncherAppWidgetInfo.RESTORE_COMPLETED
45 import com.android.launcher3.ui.TestViewHelpers
46 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
47 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
48 import com.android.launcher3.util.rule.ShellCommandRule
49 import com.android.launcher3.widget.LauncherAppWidgetHostView
50 import com.android.launcher3.widget.LauncherWidgetHolder
51 import com.android.launcher3.widget.PendingAppWidgetHostView
52 import com.android.launcher3.widget.WidgetManagerHelper
53 import java.util.concurrent.Callable
54 import org.junit.After
55 import org.junit.Assert.assertEquals
56 import org.junit.Assert.assertFalse
57 import org.junit.Assert.assertNull
58 import org.junit.Assert.assertTrue
59 import org.junit.Before
60 import org.junit.Rule
61 import org.junit.Test
62 import org.junit.runner.RunWith
63 import org.mockito.Mock
64 import org.mockito.MockitoAnnotations
65 import org.mockito.kotlin.any
66 import org.mockito.kotlin.same
67 import org.mockito.kotlin.verify
68 import org.mockito.kotlin.verifyNoMoreInteractions
69 
70 /** Tests for ItemInflater */
71 @SmallTest
72 @RunWith(AndroidJUnit4::class)
73 class ItemInflaterTest {
74 
75     @get:Rule val setFlagsRule = SetFlagsRule()
76     @get:Rule val grantWidgetRule = ShellCommandRule.grantWidgetBind()
77 
78     private val clickListener = OnClickListener {}
79     private val focusListener = OnFocusChangeListener { _, _ -> }
80 
81     @Mock private lateinit var modelWriter: ModelWriter
82 
83     private lateinit var testContext: Context
84     private lateinit var uiContext: ActivityContextWrapper
85 
86     private lateinit var widgetHolder: LauncherWidgetHolder
87     private lateinit var underTest: ItemInflater<*>
88 
89     @Before
90     fun setUp() {
91         MockitoAnnotations.initMocks(this)
92         testContext = InstrumentationRegistry.getInstrumentation().context
93 
94         uiContext = ActivityContextWrapper(getApplicationContext())
95         uiContext.setTheme(Themes.getActivityThemeRes(uiContext, 0))
96 
97         widgetHolder = LauncherWidgetHolder.newInstance(uiContext)
98         widgetHolder.startListening()
99         underTest =
100             ItemInflater(
101                 uiContext,
102                 widgetHolder,
103                 clickListener,
104                 focusListener,
105                 FrameLayout(uiContext)
106             )
107     }
108 
109     @After
110     fun tearDown() {
111         widgetHolder.destroy()
112     }
113 
114     @Test
115     fun test_workspace_item_inflated_on_UI() {
116         val itemInfo = workspaceItemInfo()
117         val view =
118             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
119 
120         assertTrue(view is BubbleTextView)
121         assertEquals(itemInfo, view!!.tag)
122     }
123 
124     @Test
125     fun test_workspace_item_inflated_on_BG() {
126         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
127 
128         val itemInfo = workspaceItemInfo()
129         val view =
130             VIEW_PREINFLATION_EXECUTOR.submit(
131                     Callable { underTest.inflateItem(itemInfo, modelWriter) }
132                 )
133                 .get()
134 
135         assertTrue(view is BubbleTextView)
136         assertEquals(itemInfo, view!!.tag)
137     }
138 
139     @Test
140     fun test_folder_inflated_on_UI() {
141         val itemInfo = FolderInfo()
142         itemInfo.add(workspaceItemInfo())
143         itemInfo.add(workspaceItemInfo())
144         itemInfo.add(workspaceItemInfo())
145 
146         val view =
147             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
148 
149         assertTrue(view is FolderIcon)
150         assertEquals(itemInfo, view!!.tag)
151     }
152 
153     @Test
154     fun test_folder_inflated_on_BG() {
155         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
156 
157         val itemInfo = FolderInfo()
158         itemInfo.add(workspaceItemInfo())
159         itemInfo.add(workspaceItemInfo())
160         itemInfo.add(workspaceItemInfo())
161 
162         val view =
163             VIEW_PREINFLATION_EXECUTOR.submit(
164                     Callable { underTest.inflateItem(itemInfo, modelWriter) }
165                 )
166                 .get()
167 
168         assertTrue(view is FolderIcon)
169         assertEquals(itemInfo, view!!.tag)
170     }
171 
172     @Test
173     fun test_app_pair_inflated_on_UI() {
174         val itemInfo = AppPairInfo()
175         itemInfo.itemType = ITEM_TYPE_APP_PAIR
176         itemInfo.add(workspaceItemInfo())
177         itemInfo.add(workspaceItemInfo())
178 
179         val view =
180             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
181 
182         assertTrue(view is AppPairIcon)
183         assertEquals(itemInfo, view!!.tag)
184     }
185 
186     @Test
187     fun test_app_pair_inflated_on_BG() {
188         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
189 
190         val itemInfo = AppPairInfo()
191         itemInfo.itemType = ITEM_TYPE_APP_PAIR
192         itemInfo.add(workspaceItemInfo())
193         itemInfo.add(workspaceItemInfo())
194 
195         val view =
196             VIEW_PREINFLATION_EXECUTOR.submit(
197                     Callable { underTest.inflateItem(itemInfo, modelWriter) }
198                 )
199                 .get()
200 
201         assertTrue(view is AppPairIcon)
202         assertEquals(itemInfo, view!!.tag)
203     }
204 
205     @Test
206     fun test_pending_widget_inflated_on_UI() {
207         val itemInfo = widgetItemInfo(true)
208 
209         val view =
210             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
211 
212         assertTrue(view is PendingAppWidgetHostView)
213         assertEquals(itemInfo, view!!.tag)
214     }
215 
216     @Test
217     fun test_pending_widget_inflated_on_BG() {
218         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
219 
220         val itemInfo = widgetItemInfo(true)
221         val view =
222             VIEW_PREINFLATION_EXECUTOR.submit(
223                     Callable { underTest.inflateItem(itemInfo, modelWriter) }
224                 )
225                 .get()
226 
227         assertTrue(view is PendingAppWidgetHostView)
228         assertEquals(itemInfo, view!!.tag)
229     }
230 
231     @Test
232     fun test_widget_restored_and_inflated_on_UI() {
233         val itemInfo = widgetItemInfo(false)
234 
235         val view =
236             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
237 
238         // Verify that the widget is automatically restored and a final widget is returned
239         assertTrue(view is LauncherAppWidgetHostView)
240         assertFalse(view is PendingAppWidgetHostView)
241         assertEquals(itemInfo, view!!.tag)
242         assertEquals(RESTORE_COMPLETED, itemInfo.restoreStatus)
243         verify(modelWriter).updateItemInDatabase(same(itemInfo))
244     }
245 
246     @Test
247     fun test_widget_restored_and_inflated_on_BG() {
248         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
249         val itemInfo = widgetItemInfo(false)
250 
251         val view =
252             VIEW_PREINFLATION_EXECUTOR.submit(
253                     Callable { underTest.inflateItem(itemInfo, modelWriter) }
254                 )
255                 .get()
256 
257         // Verify that the widget is automatically restored and a final widget is returned
258         assertTrue(view is LauncherAppWidgetHostView)
259         assertFalse(view is PendingAppWidgetHostView)
260         assertEquals(itemInfo, view!!.tag)
261         assertEquals(RESTORE_COMPLETED, itemInfo.restoreStatus)
262         verify(modelWriter).updateItemInDatabase(same(itemInfo))
263     }
264 
265     @Test
266     fun test_invalid_widget_deleted() {
267         val itemInfo =
268             widgetItemInfo(false).apply {
269                 providerName = ComponentName(providerName.packageName, "invalid_provider_name")
270             }
271         val view =
272             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
273         assertNull(view)
274         verify(modelWriter).deleteItemFromDatabase(same(itemInfo), any())
275     }
276 
277     @Test
278     fun test_normal_widget_inflated_UI() {
279         val providerInfo = TestViewHelpers.findWidgetProvider(false)
280         val id = widgetHolder.allocateAppWidgetId()
281         assertTrue(
282             WidgetManagerHelper(uiContext).bindAppWidgetIdIfAllowed(id, providerInfo, Bundle())
283         )
284         val itemInfo = LauncherAppWidgetInfo(id, providerInfo.provider)
285         itemInfo.spanX = 2
286         itemInfo.spanY = 2
287 
288         val view =
289             MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
290 
291         // Verify that the widget is automatically restored and a final widget is returned
292         assertTrue(view is LauncherAppWidgetHostView)
293         assertFalse(view is PendingAppWidgetHostView)
294         assertEquals(itemInfo, view!!.tag)
295         verifyNoMoreInteractions(modelWriter)
296     }
297 
298     private fun workspaceItemInfo() =
299         AppInfo(
300                 uiContext,
301                 uiContext
302                     .getSystemService(LauncherApps::class.java)!!
303                     .getActivityList(testContext.packageName, Process.myUserHandle())[0],
304                 Process.myUserHandle()
305             )
306             .makeWorkspaceItem(uiContext)
307 
308     private fun widgetItemInfo(hasConfig: Boolean) =
309         LauncherAppWidgetInfo(0, TestViewHelpers.findWidgetProvider(hasConfig).component).apply {
310             spanX = 2
311             spanY = 2
312             restoreStatus = FLAG_ID_NOT_VALID or FLAG_UI_NOT_READY
313         }
314 }
315