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