1 /* 2 * 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.model 18 19 import android.app.prediction.AppTarget 20 import android.app.prediction.AppTargetEvent 21 import android.app.prediction.AppTargetId 22 import android.appwidget.AppWidgetProviderInfo 23 import android.content.ComponentName 24 import android.content.Context 25 import android.os.Process.myUserHandle 26 import android.os.UserHandle 27 import androidx.test.core.app.ApplicationProvider 28 import androidx.test.ext.junit.runners.AndroidJUnit4 29 import com.android.launcher3.DeviceProfile 30 import com.android.launcher3.InvariantDeviceProfile 31 import com.android.launcher3.LauncherAppState 32 import com.android.launcher3.icons.IconCache 33 import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession 34 import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions 35 import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter 36 import com.android.launcher3.util.ActivityContextWrapper 37 import com.android.launcher3.util.PackageUserKey 38 import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo 39 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo 40 import com.google.common.truth.Truth.assertThat 41 import java.util.function.Predicate 42 import junit.framework.Assert.assertNotNull 43 import org.junit.Before 44 import org.junit.Test 45 import org.junit.runner.RunWith 46 import org.mockito.Mock 47 import org.mockito.MockitoAnnotations 48 49 @RunWith(AndroidJUnit4::class) 50 class WidgetsPredictionsRequesterTest { 51 52 private lateinit var mUserHandle: UserHandle 53 private lateinit var context: Context 54 private lateinit var deviceProfile: DeviceProfile 55 private lateinit var testInvariantProfile: InvariantDeviceProfile 56 57 private lateinit var widget1aInfo: AppWidgetProviderInfo 58 private lateinit var widget1bInfo: AppWidgetProviderInfo 59 private lateinit var widget2Info: AppWidgetProviderInfo 60 61 private lateinit var widgetItem1a: WidgetItem 62 private lateinit var widgetItem1b: WidgetItem 63 private lateinit var widgetItem2: WidgetItem 64 65 private lateinit var allWidgets: Map<PackageUserKey, List<WidgetItem>> 66 67 @Mock private lateinit var iconCache: IconCache 68 69 @Before setUpnull70 fun setUp() { 71 MockitoAnnotations.initMocks(this) 72 mUserHandle = myUserHandle() 73 context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) 74 testInvariantProfile = LauncherAppState.getIDP(context) 75 deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context) 76 77 widget1aInfo = 78 createAppWidgetProviderInfo( 79 ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_A_CLASS_NAME) 80 ) 81 widget1bInfo = 82 createAppWidgetProviderInfo( 83 ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME) 84 ) 85 widgetItem1a = createWidgetItem(widget1aInfo) 86 widgetItem1b = createWidgetItem(widget1bInfo) 87 88 widget2Info = 89 createAppWidgetProviderInfo( 90 ComponentName.createRelative(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME) 91 ) 92 widgetItem2 = createWidgetItem(widget2Info) 93 94 allWidgets = 95 mapOf( 96 PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to 97 listOf(widgetItem1a, widgetItem1b), 98 PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2), 99 ) 100 } 101 102 @Test buildBundleForPredictionSession_includesAddedAppWidgetsnull103 fun buildBundleForPredictionSession_includesAddedAppWidgets() { 104 val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info) 105 106 val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE) 107 val addedWidgetsBundleExtra = 108 bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java) 109 110 assertNotNull(addedWidgetsBundleExtra) 111 assertThat(addedWidgetsBundleExtra) 112 .containsExactly( 113 buildExpectedAppTargetEvent( 114 /*pkg=*/ APP_1_PACKAGE_NAME, 115 /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME, 116 /*user=*/ mUserHandle 117 ), 118 buildExpectedAppTargetEvent( 119 /*pkg=*/ APP_1_PACKAGE_NAME, 120 /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME, 121 /*user=*/ mUserHandle 122 ), 123 buildExpectedAppTargetEvent( 124 /*pkg=*/ APP_2_PACKAGE_NAME, 125 /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME, 126 /*user=*/ mUserHandle 127 ) 128 ) 129 } 130 131 @Test filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictionsnull132 fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() { 133 val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) 134 val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface) 135 136 val predictions = 137 listOf( 138 // already on surface 139 AppTarget( 140 AppTargetId(APP_1_PACKAGE_NAME), 141 APP_1_PACKAGE_NAME, 142 APP_1_PROVIDER_B_CLASS_NAME, 143 mUserHandle 144 ), 145 // eligible 146 AppTarget( 147 AppTargetId(APP_2_PACKAGE_NAME), 148 APP_2_PACKAGE_NAME, 149 APP_2_PROVIDER_1_CLASS_NAME, 150 mUserHandle 151 ) 152 ) 153 154 // only 2 was eligible 155 assertThat(filterPredictions(predictions, allWidgets, filter)).containsExactly(widgetItem2) 156 } 157 158 @Test filterPredictions_appPredictions_returnsWidgetFromPackagenull159 fun filterPredictions_appPredictions_returnsWidgetFromPackage() { 160 val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo) 161 val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface) 162 163 val predictions = 164 listOf( 165 AppTarget( 166 AppTargetId(APP_1_PACKAGE_NAME), 167 APP_1_PACKAGE_NAME, 168 "$APP_1_PACKAGE_NAME.SomeActivity", 169 mUserHandle 170 ), 171 AppTarget( 172 AppTargetId(APP_2_PACKAGE_NAME), 173 APP_2_PACKAGE_NAME, 174 "$APP_2_PACKAGE_NAME.SomeActivity2", 175 mUserHandle 176 ), 177 ) 178 179 assertThat(filterPredictions(predictions, allWidgets, filter)) 180 .containsExactly(widgetItem1a, widgetItem2) 181 } 182 createWidgetItemnull183 private fun createWidgetItem( 184 providerInfo: AppWidgetProviderInfo, 185 ): WidgetItem { 186 val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo) 187 return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context) 188 } 189 190 companion object { 191 const val TEST_UI_SURFACE = "widgets_test" 192 const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets" 193 194 const val APP_1_PACKAGE_NAME = "com.example.app1" 195 const val APP_1_PROVIDER_A_CLASS_NAME = "app1Provider1" 196 const val APP_1_PROVIDER_B_CLASS_NAME = "app1Provider2" 197 198 const val APP_2_PACKAGE_NAME = "com.example.app2" 199 const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1" 200 201 const val TEST_PACKAGE = "pkg" 202 buildExpectedAppTargetEventnull203 private fun buildExpectedAppTargetEvent( 204 pkg: String, 205 providerClassName: String, 206 userHandle: UserHandle 207 ): AppTargetEvent { 208 val appTarget = 209 AppTarget.Builder( 210 /*id=*/ AppTargetId("widget:$pkg"), 211 /*packageName=*/ pkg, 212 /*user=*/ userHandle 213 ) 214 .setClassName(providerClassName) 215 .build() 216 return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN) 217 .setLaunchLocation(TEST_UI_SURFACE) 218 .build() 219 } 220 } 221 } 222