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