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