• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 package com.android.launcher3.model;
17 
18 import static android.content.pm.ApplicationInfo.CATEGORY_PRODUCTIVITY;
19 import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
20 import static android.os.Process.myUserHandle;
21 
22 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
23 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
24 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
25 import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
26 import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.ArgumentMatchers.anyString;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.doAnswer;
35 import static org.mockito.Mockito.doReturn;
36 
37 import android.app.prediction.AppTarget;
38 import android.app.prediction.AppTargetId;
39 import android.appwidget.AppWidgetManager;
40 import android.appwidget.AppWidgetProviderInfo;
41 import android.content.ComponentName;
42 import android.content.pm.ApplicationInfo;
43 import android.content.pm.LauncherApps;
44 import android.os.UserHandle;
45 import android.platform.test.flag.junit.SetFlagsRule;
46 import android.text.TextUtils;
47 
48 import androidx.test.core.content.pm.ApplicationInfoBuilder;
49 import androidx.test.ext.junit.runners.AndroidJUnit4;
50 import androidx.test.filters.SmallTest;
51 
52 import com.android.launcher3.Flags;
53 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
54 import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
55 import com.android.launcher3.util.LauncherLayoutBuilder;
56 import com.android.launcher3.util.LauncherModelHelper;
57 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
58 import com.android.launcher3.widget.PendingAddWidgetInfo;
59 
60 import org.junit.After;
61 import org.junit.Before;
62 import org.junit.Rule;
63 import org.junit.Test;
64 import org.junit.runner.RunWith;
65 
66 import java.util.Arrays;
67 import java.util.List;
68 import java.util.stream.Collectors;
69 
70 @SmallTest
71 @RunWith(AndroidJUnit4.class)
72 public final class WidgetsPredicationUpdateTaskTest {
73 
74     @Rule
75     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
76 
77     private AppWidgetProviderInfo mApp1Provider1;
78     private AppWidgetProviderInfo mApp1Provider2;
79     private AppWidgetProviderInfo mApp2Provider1;
80     private AppWidgetProviderInfo mApp4Provider1;
81     private AppWidgetProviderInfo mApp4Provider2;
82     private AppWidgetProviderInfo mApp5Provider1;
83     private List<AppWidgetProviderInfo> allWidgets;
84 
85     private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
86     private LauncherModelHelper mModelHelper;
87     private UserHandle mUserHandle;
88     private LauncherApps mLauncherApps;
89 
90 
91     @Before
setup()92     public void setup() throws Exception {
93         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_CATEGORIZED_WIDGET_SUGGESTIONS);
94         mModelHelper = new LauncherModelHelper();
95 
96         mUserHandle = myUserHandle();
97         mApp1Provider1 = createAppWidgetProviderInfo(
98                 ComponentName.createRelative("app1", "provider1"));
99         mApp1Provider2 = createAppWidgetProviderInfo(
100                 ComponentName.createRelative("app1", "provider2"));
101         mApp2Provider1 = createAppWidgetProviderInfo(
102                 ComponentName.createRelative("app2", "provider1"));
103         mApp4Provider1 = createAppWidgetProviderInfo(
104                 ComponentName.createRelative("app4", "provider1"));
105         mApp4Provider2 = createAppWidgetProviderInfo(
106                 ComponentName.createRelative("app4", ".provider2"));
107         mApp5Provider1 = createAppWidgetProviderInfo(
108                 ComponentName.createRelative("app5", "provider1"));
109         allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
110                 mApp4Provider1, mApp4Provider2, mApp5Provider1);
111 
112         mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
113         doAnswer(i -> {
114             String pkg = i.getArgument(0);
115             ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
116                     .setPackageName(pkg)
117                     .setName("App " + pkg)
118                     .build();
119             applicationInfo.category = CATEGORY_PRODUCTIVITY;
120             applicationInfo.flags = FLAG_INSTALLED;
121             return applicationInfo;
122         }).when(mLauncherApps).getApplicationInfo(anyString(), anyInt(), any());
123 
124         AppWidgetManager manager = mModelHelper.sandboxContext.spyService(AppWidgetManager.class);
125         doReturn(allWidgets).when(manager).getInstalledProviders();
126         doReturn(allWidgets).when(manager).getInstalledProvidersForProfile(eq(myUserHandle()));
127         doAnswer(i -> {
128             String pkg = i.getArgument(0);
129             return TextUtils.isEmpty(pkg) ? allWidgets : allWidgets.stream()
130                     .filter(a -> pkg.equals(a.provider.getPackageName()))
131                     .collect(Collectors.toList());
132         }).when(manager).getInstalledProvidersForPackage(any(), eq(myUserHandle()));
133 
134         LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
135                 .atWorkspace(0, 1, 2).putWidget("app4", "provider1", 1, 1)
136                 .atWorkspace(0, 1, 3).putWidget("app5", "provider1", 1, 1);
137         mModelHelper.setupDefaultLayoutProvider(builder);
138         MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(mCallback)).get();
139         mModelHelper.loadModelSync();
140     }
141 
142     @After
tearDown()143     public void tearDown() {
144         mModelHelper.destroy();
145     }
146 
147     @Test
widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder()148     public void widgetsRecommendationRan_shouldOnlyReturnNotAddedWidgetsInAppPredictionOrder() {
149         // Run on model executor so that no other task runs in the middle.
150         runOnExecutorSync(MODEL_EXECUTOR, () -> {
151             // WHEN newPredicationTask is executed with app predication of 5 apps.
152             AppTarget app1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
153                     mUserHandle);
154             AppTarget app2 = new AppTarget(new AppTargetId("app2"), "app2", "provider1",
155                     mUserHandle);
156             AppTarget app3 = new AppTarget(new AppTargetId("app3"), "app3", "className",
157                     mUserHandle);
158             AppTarget app4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
159                     mUserHandle);
160             AppTarget app5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
161                     mUserHandle);
162             mCallback.mRecommendedWidgets = null;
163             mModelHelper.getModel().enqueueModelUpdateTask(
164                     newWidgetsPredicationTask(List.of(app5, app3, app2, app4, app1)));
165             runOnExecutorSync(MAIN_EXECUTOR, () -> { });
166 
167             // THEN only 2 widgets are returned because
168             // 1. app5/provider1 & app4/provider1 have already been added to workspace. They are
169             //    excluded from the result.
170             // 2. app3 doesn't have a widget.
171             // 3. only 1 widget is picked from app1 because we only want to promote one widget
172             // per app.
173             List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
174                     .stream()
175                     .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
176                     .collect(Collectors.toList());
177             assertThat(recommendedWidgets).hasSize(2);
178             recommendedWidgets.forEach(pendingAddWidgetInfo ->
179                     assertThat(pendingAddWidgetInfo.recommendationCategory).isNotNull()
180             );
181             assertWidgetInfo(recommendedWidgets.get(0).info, mApp2Provider1);
182             assertWidgetInfo(recommendedWidgets.get(1).info, mApp1Provider1);
183         });
184     }
185 
186     @Test
widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty()187     public void widgetsRecommendationRan_shouldReturnEmptyWidgetsWhenEmpty() {
188         runOnExecutorSync(MODEL_EXECUTOR, () -> {
189 
190             // Not installed widget
191             AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider3",
192                     mUserHandle);
193             // Not installed app
194             AppTarget widget3 = new AppTarget(new AppTargetId("app2"), "app3", "provider1",
195                     mUserHandle);
196             // Workspace added widgets
197             AppTarget widget4 = new AppTarget(new AppTargetId("app4"), "app4", "provider1",
198                     mUserHandle);
199             AppTarget widget5 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
200                     mUserHandle);
201 
202             mCallback.mRecommendedWidgets = null;
203             mModelHelper.getModel().enqueueModelUpdateTask(
204                     newWidgetsPredicationTask(List.of(widget5, widget3, widget4, widget1)));
205             runOnExecutorSync(MAIN_EXECUTOR, () -> { });
206 
207             // Only widgets suggested by prediction system are returned.
208             List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
209                     .stream()
210                     .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
211                     .collect(Collectors.toList());
212             assertThat(recommendedWidgets).hasSize(0);
213         });
214     }
215 
assertWidgetInfo( LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected)216     private void assertWidgetInfo(
217             LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
218         assertThat(actual.provider).isEqualTo(expected.provider);
219         assertThat(actual.getUser()).isEqualTo(expected.getProfile());
220     }
221 
newWidgetsPredicationTask(List<AppTarget> appTargets)222     private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
223         return new WidgetsPredictionUpdateTask(
224                 new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
225                 appTargets);
226     }
227 
228     private final class FakeBgDataModelCallback implements BgDataModel.Callbacks {
229 
230         private FixedContainerItems mRecommendedWidgets = null;
231 
232         @Override
bindExtraContainerItems(FixedContainerItems item)233         public void bindExtraContainerItems(FixedContainerItems item) {
234             mRecommendedWidgets = item;
235         }
236     }
237 }
238