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