1 /*
2  * Copyright (C) 2020 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 android.ext.services.resolver;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.doNothing;
23 import static org.mockito.Mockito.when;
24 import static org.testng.Assert.assertThrows;
25 
26 import android.content.Context;
27 import android.content.ContextWrapper;
28 import android.content.Intent;
29 import android.content.SharedPreferences;
30 import android.os.UserManager;
31 import android.service.resolver.ResolverTarget;
32 import android.test.ServiceTestCase;
33 import android.util.ArrayMap;
34 
35 import com.google.common.collect.Range;
36 
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.mockito.Mock;
40 import org.mockito.Mockito;
41 import org.mockito.MockitoAnnotations;
42 
43 import java.util.Arrays;
44 import java.util.List;
45 
46 public class LRResolverRankerServiceTest extends ServiceTestCase<LRResolverRankerService> {
47     private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
48     private static final String BIAS_PREF_KEY = "bias";
49     private static final String VERSION_PREF_KEY = "version";
50     private static final String LAUNCH_SCORE = "launch";
51     private static final String TIME_SPENT_SCORE = "timeSpent";
52     private static final String RECENCY_SCORE = "recency";
53     private static final String CHOOSER_SCORE = "chooser";
54 
55     @Mock private Context mContext;
56     @Mock private SharedPreferences mSharedPreferences;
57     @Mock private SharedPreferences.Editor mEditor;
58     @Mock private UserManager mUserManager;
59 
LRResolverRankerServiceTest()60     public LRResolverRankerServiceTest() {
61         super(LRResolverRankerService.class);
62     }
63 
64     @Before
setUp()65     public void setUp() throws Exception {
66         super.setUp();
67         MockitoAnnotations.initMocks(this);
68         mContext = Mockito.spy(new ContextWrapper(getSystemContext()));
69         setContext(mContext);
70 
71         Intent intent = new Intent(getContext(), LRResolverRankerService.class);
72         startService(intent);
73     }
74 
75     @Test
testInitModelUnderUserLocked()76     public void testInitModelUnderUserLocked() {
77         setUserLockedStatus(true);
78         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
79 
80         final LRResolverRankerService service = getService();
81         service.onBind(new Intent("test"));
82 
83         assertThat(service.mFeatureWeights).isNull();
84     }
85 
86     @Test
testInitModelWhileNoSharedPreferences()87     public void testInitModelWhileNoSharedPreferences() {
88         setUserLockedStatus(false);
89         createSharedPreferences(/* hasSharedPreferences */ false, /* version */ 0);
90 
91         final LRResolverRankerService service = getService();
92         service.onBind(new Intent("test"));
93 
94         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
95         assertThat(featureWeights.get(LAUNCH_SCORE)).isEqualTo(2.5543f);
96         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isEqualTo(2.8412f);
97         assertThat(featureWeights.get(RECENCY_SCORE)).isEqualTo(0.269f);
98         assertThat(featureWeights.get(CHOOSER_SCORE)).isEqualTo(4.2222f);
99     }
100 
101     @Test
testInitModelWhileVersionInSharedPreferencesSmallerThanCurrent()102     public void testInitModelWhileVersionInSharedPreferencesSmallerThanCurrent() {
103         setUserLockedStatus(false);
104         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 0);
105 
106         final LRResolverRankerService service = getService();
107         service.onBind(new Intent("test"));
108 
109         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
110         assertThat(featureWeights.get(LAUNCH_SCORE)).isEqualTo(2.5543f);
111         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isEqualTo(2.8412f);
112         assertThat(featureWeights.get(RECENCY_SCORE)).isEqualTo(0.269f);
113         assertThat(featureWeights.get(CHOOSER_SCORE)).isEqualTo(4.2222f);
114     }
115 
116     @Test
testInitModelWhileVersionInSharedPreferencesNotSmallerThanCurrent()117     public void testInitModelWhileVersionInSharedPreferencesNotSmallerThanCurrent() {
118         setUserLockedStatus(false);
119         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
120 
121         final LRResolverRankerService service = getService();
122         service.onBind(new Intent("test"));
123 
124         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
125         assertThat(featureWeights.get(LAUNCH_SCORE)).isEqualTo(0.1f);
126         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isEqualTo(0.2f);
127         assertThat(featureWeights.get(RECENCY_SCORE)).isEqualTo(0.3f);
128         assertThat(featureWeights.get(CHOOSER_SCORE)).isEqualTo(0.4f);
129     }
130 
131     @Test
testOnPredictSharingProbabilitiesWhileServiceIsNotReady()132     public void testOnPredictSharingProbabilitiesWhileServiceIsNotReady() {
133         setUserLockedStatus(true);
134         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
135         final LRResolverRankerService service = getService();
136         service.onBind(new Intent("test"));
137         final List<ResolverTarget> targets =
138                 Arrays.asList(
139                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.2f,
140                                 /* launch */ 0.3f, /* chooser */ 0.4f, /* selectProb */ -1.0f),
141                         makeNewResolverTarget(/* recency */ 0.4f, /* timeSpent */ 0.3f,
142                                 /* launch */ 0.2f, /* chooser */ 0.1f, /* selectProb */ -1.0f));
143 
144         assertThrows(IllegalStateException.class,
145                 () -> service.onPredictSharingProbabilities(targets));
146     }
147 
148     @Test
testOnPredictSharingProbabilitiesAfterUserUnlock()149     public void testOnPredictSharingProbabilitiesAfterUserUnlock() {
150         setUserLockedStatus(true);
151         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
152         final LRResolverRankerService service = getService();
153         service.onBind(new Intent("test"));
154         setUserLockedStatus(false);
155         final List<ResolverTarget> targets =
156                 Arrays.asList(
157                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.2f,
158                                 /* launch */ 0.3f, /* chooser */ 0.4f, /* selectProb */ -1.0f),
159                         makeNewResolverTarget(/* recency */ 0.4f, /* timeSpent */ 0.3f,
160                                 /* launch */ 0.2f, /* chooser */ 0.1f, /* selectProb */ -1.0f));
161 
162         service.onPredictSharingProbabilities(targets);
163 
164         assertThat(targets.get(0).getSelectProbability()).isIn(Range.closed(0f, 1.0f));
165         assertThat(targets.get(1).getSelectProbability()).isIn(Range.closed(0f, 1.0f));
166     }
167 
168     @Test
testOnPredictSharingProbabilities()169     public void testOnPredictSharingProbabilities() {
170         setUserLockedStatus(false);
171         createSharedPreferences(/* hasSharedPreferences */ false, /* version */ 0);
172         final LRResolverRankerService service = getService();
173         service.onBind(new Intent("test"));
174         final List<ResolverTarget> targets =
175                 Arrays.asList(
176                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.2f,
177                                 /* launch */ 0.3f, /* chooser */ 0.4f, /* selectProb */ -1.0f),
178                         makeNewResolverTarget(/* recency */ 0.4f, /* timeSpent */ 0.3f,
179                                 /* launch */ 0.2f, /* chooser */ 0.1f, /* selectProb */ -1.0f));
180 
181         service.onPredictSharingProbabilities(targets);
182 
183         assertThat(targets.get(0).getSelectProbability()).isIn(Range.closed(0f, 1.0f));
184         assertThat(targets.get(1).getSelectProbability()).isIn(Range.closed(0f, 1.0f));
185     }
186 
187     @Test
testOnTrainRankingModelWhileServiceIsNotReady()188     public void testOnTrainRankingModelWhileServiceIsNotReady() {
189         setUserLockedStatus(true);
190         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
191         final LRResolverRankerService service = getService();
192         service.onBind(new Intent("test"));
193         final List<ResolverTarget> targets =
194                 Arrays.asList(
195                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.1f,
196                                 /* launch */ 0.1f, /* chooser */ 0.1f, /* selectProb */ 0.1f),
197                         makeNewResolverTarget(/* recency */ 0.2f, /* timeSpent */ 0.2f,
198                                 /* launch */ 0.2f, /* chooser */ 0.2f, /* selectProb */ 0.2f),
199                         makeNewResolverTarget(/* recency */ 0.3f, /* timeSpent */ 0.3f,
200                                 /* launch */ 0.3f, /* chooser */ 0.3f, /* selectProb */ 0.3f));
201 
202         assertThrows(IllegalStateException.class,
203                 () -> service.onTrainRankingModel(targets, /* selectedPosition */ 0));
204     }
205 
206     @Test
testOnTrainRankingModelAfterUserUnlock()207     public void testOnTrainRankingModelAfterUserUnlock() {
208         setUserLockedStatus(true);
209         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
210         final LRResolverRankerService service = getService();
211         service.onBind(new Intent("test"));
212         setUserLockedStatus(false);
213         final List<ResolverTarget> targets =
214                 Arrays.asList(
215                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.1f,
216                                 /* launch */ 0.1f, /* chooser */ 0.1f, /* selectProb */ 0.1f),
217                         makeNewResolverTarget(/* recency */ 0.2f, /* timeSpent */ 0.2f,
218                                 /* launch */ 0.2f, /* chooser */ 0.2f, /* selectProb */ 0.2f),
219                         makeNewResolverTarget(/* recency */ 0.3f, /* timeSpent */ 0.3f,
220                                 /* launch */ 0.3f, /* chooser */ 0.3f, /* selectProb */ 0.3f));
221 
222         service.onTrainRankingModel(targets, /* selectedPosition */ 0);
223 
224         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
225         assertThat(featureWeights.get(LAUNCH_SCORE)).isNotEqualTo(0.1f);
226         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isNotEqualTo(0.2f);
227         assertThat(featureWeights.get(RECENCY_SCORE)).isNotEqualTo(0.3f);
228         assertThat(featureWeights.get(CHOOSER_SCORE)).isNotEqualTo(0.4f);
229     }
230 
231     @Test
testOnTrainRankingModelWhileSelectedPositionScoreIsNotHighest()232     public void testOnTrainRankingModelWhileSelectedPositionScoreIsNotHighest() {
233         setUserLockedStatus(false);
234         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
235         final LRResolverRankerService service = getService();
236         service.onBind(new Intent("test"));
237         final List<ResolverTarget> targets =
238                 Arrays.asList(
239                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.1f,
240                                 /* launch */ 0.1f, /* chooser */ 0.1f, /* selectProb */ 0.1f),
241                         makeNewResolverTarget(/* recency */ 0.2f, /* timeSpent */ 0.2f,
242                                 /* launch */ 0.2f, /* chooser */ 0.2f, /* selectProb */ 0.2f),
243                         makeNewResolverTarget(/* recency */ 0.3f, /* timeSpent */ 0.3f,
244                                 /* launch */ 0.3f, /* chooser */ 0.3f, /* selectProb */ 0.3f));
245 
246         service.onTrainRankingModel(targets, /* selectedPosition */ 0);
247 
248         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
249         assertThat(featureWeights.get(LAUNCH_SCORE)).isNotEqualTo(0.1f);
250         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isNotEqualTo(0.2f);
251         assertThat(featureWeights.get(RECENCY_SCORE)).isNotEqualTo(0.3f);
252         assertThat(featureWeights.get(CHOOSER_SCORE)).isNotEqualTo(0.4f);
253     }
254 
255     @Test
testOnTrainRankingModelWhileSelectedPositionScoreIsHighest()256     public void testOnTrainRankingModelWhileSelectedPositionScoreIsHighest() {
257         setUserLockedStatus(false);
258         createSharedPreferences(/* hasSharedPreferences */ true, /* version */ 1);
259         final LRResolverRankerService service = getService();
260         service.onBind(new Intent("test"));
261         final List<ResolverTarget> targets =
262                 Arrays.asList(
263                         makeNewResolverTarget(/* recency */ 0.1f, /* timeSpent */ 0.1f,
264                                 /* launch */ 0.1f, /* chooser */ 0.1f, /* selectProb */ 0.1f),
265                         makeNewResolverTarget(/* recency */ 0.2f, /* timeSpent */ 0.2f,
266                                 /* launch */ 0.2f, /* chooser */ 0.2f, /* selectProb */ 0.2f),
267                         makeNewResolverTarget(/* recency */ 0.3f, /* timeSpent */ 0.3f,
268                                 /* launch */ 0.3f, /* chooser */ 0.3f, /* selectProb */ 0.3f));
269 
270         service.onTrainRankingModel(targets, /* selectedPosition */ 2);
271 
272         final ArrayMap<String, Float> featureWeights = service.mFeatureWeights;
273         assertThat(featureWeights.get(LAUNCH_SCORE)).isEqualTo(0.1f);
274         assertThat(featureWeights.get(TIME_SPENT_SCORE)).isEqualTo(0.2f);
275         assertThat(featureWeights.get(RECENCY_SCORE)).isEqualTo(0.3f);
276         assertThat(featureWeights.get(CHOOSER_SCORE)).isEqualTo(0.4f);
277     }
278 
makeNewResolverTarget(float recency, float timeSpent, float launch, float chooser, float selectProb)279     private ResolverTarget makeNewResolverTarget(float recency, float timeSpent, float launch,
280             float chooser, float selectProb) {
281         ResolverTarget target = new ResolverTarget();
282         target.setRecencyScore(recency);
283         target.setTimeSpentScore(timeSpent);
284         target.setLaunchScore(launch);
285         target.setChooserScore(chooser);
286         target.setSelectProbability(selectProb);
287         return target;
288     }
289 
createSharedPreferences(boolean hasSharedPreferences, int version)290     private void createSharedPreferences(boolean hasSharedPreferences, int version) {
291         when(mContext.createCredentialProtectedStorageContext()).thenReturn(mContext);
292         if (!hasSharedPreferences) {
293             when(mContext.getSharedPreferences(/* name */ PARAM_SHARED_PREF_NAME + ".xml",
294                     Context.MODE_PRIVATE)).thenReturn(null);
295         } else {
296             when(mContext.getSharedPreferences(/* name */ PARAM_SHARED_PREF_NAME + ".xml",
297                     Context.MODE_PRIVATE)).thenReturn(mSharedPreferences);
298             when(mSharedPreferences.getInt(VERSION_PREF_KEY, 0)).thenReturn(version);
299             when(mSharedPreferences.getFloat(BIAS_PREF_KEY, 0)).thenReturn(0.0f);
300             when(mSharedPreferences.getFloat(LAUNCH_SCORE, 0)).thenReturn(0.1f);
301             when(mSharedPreferences.getFloat(TIME_SPENT_SCORE, 0)).thenReturn(0.2f);
302             when(mSharedPreferences.getFloat(RECENCY_SCORE, 0)).thenReturn(0.3f);
303             when(mSharedPreferences.getFloat(CHOOSER_SCORE, 0)).thenReturn(0.4f);
304             when(mSharedPreferences.edit()).thenReturn(mEditor);
305             doNothing().when(mEditor).apply();
306         }
307     }
308 
setUserLockedStatus(boolean locked)309     private void setUserLockedStatus(boolean locked) {
310         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
311         when(mUserManager.isUserUnlocked()).thenReturn(!locked);
312     }
313 }
314