1 /*
2  * Copyright (C) 2019 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 android.apppredictionservice.cts;
17 
18 import static android.apppredictionservice.cts.PredictionService.EXTRA_REPORTER;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotEquals;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.app.prediction.AppPredictionContext;
27 import android.app.prediction.AppPredictionManager;
28 import android.app.prediction.AppPredictor;
29 import android.app.prediction.AppTarget;
30 import android.app.prediction.AppTargetEvent;
31 import android.app.prediction.AppTargetId;
32 import android.apppredictionservice.cts.ServiceReporter.RequestServiceFeaturesVerifier;
33 import android.apppredictionservice.cts.ServiceReporter.RequestVerifier;
34 import android.content.Context;
35 import android.os.Bundle;
36 import android.os.SystemClock;
37 import android.os.UserHandle;
38 import android.platform.test.annotations.RequiresFlagsEnabled;
39 import android.platform.test.flag.junit.CheckFlagsRule;
40 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
41 import android.service.appprediction.flags.Flags;
42 import android.util.Log;
43 
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.compatibility.common.util.RequiredServiceRule;
48 import com.android.compatibility.common.util.SystemUtil;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 
56 import java.util.ArrayList;
57 import java.util.Collections;
58 import java.util.List;
59 import java.util.concurrent.Executors;
60 
61 /**
62  * atest CtsAppPredictionServiceTestCases:AppPredictionServiceTest
63  */
64 @RunWith(AndroidJUnit4.class)
65 public class AppPredictionServiceTest {
66 
67     private static final String TAG = "AppPredictionServiceTest";
68 
69     @Rule
70     public final CheckFlagsRule mCheckFlagsRule =
71             DeviceFlagsValueProvider.createCheckFlagsRule();
72 
73     private static final String APP_PREDICTION_SERVICE = "app_prediction";
74 
75     private static final String TEST_UI_SURFACE = "testSysUiSurface";
76     private static final int TEST_NUM_PREDICTIONS = 10;
77     private static final String TEST_LAUNCH_LOCATION = "testCollapsedLocation";
78     private static final int TEST_ACTION = 2;
79 
80     @Rule
81     public final RequiredServiceRule mRequiredServiceRule =
82             new RequiredServiceRule(APP_PREDICTION_SERVICE);
83 
84     private ServiceReporter mReporter;
85     private Bundle mPredictionContextExtras;
86 
87     @Before
setUp()88     public void setUp() throws Exception {
89         // Enable the prediction service
90         setService(PredictionService.SERVICE_NAME);
91         mReporter = new ServiceReporter();
92         mPredictionContextExtras = new Bundle();
93         mPredictionContextExtras.putBinder(EXTRA_REPORTER, mReporter);
94     }
95 
96     @After
tearDown()97     public void tearDown() throws Exception {
98         // Reset the prediction service
99         setService(null);
100         mReporter = null;
101         SystemClock.sleep(1000);
102     }
103 
104     @Test
testCreateDestroySession()105     public void testCreateDestroySession() {
106         AppPredictionContext context =  createTestPredictionContext();
107         AppPredictor client = createTestPredictor(context);
108 
109         // Wait for the service to bind and the session to be created
110         mReporter.awaitOnCreatePredictionSession();
111         mReporter.assertActiveSession(client.getSessionId());
112         assertEquals(mReporter.getPredictionContext(client.getSessionId()), context);
113 
114         // Create another session, and ensure that the session ids differ
115         AppPredictionContext context2 =  createTestPredictionContext();
116         AppPredictor client2 = createTestPredictor(context2);
117 
118         mReporter.awaitOnCreatePredictionSession();
119         mReporter.assertActiveSession(client2.getSessionId());
120         assertEquals(mReporter.getPredictionContext(client2.getSessionId()), context2);
121         assertNotEquals(client.getSessionId(), client2.getSessionId());
122 
123         // Destroy both sessions
124         client.destroy();
125         mReporter.awaitOnDestroyPredictionSession();
126         client2.destroy();
127         mReporter.awaitOnDestroyPredictionSession();
128 
129         // Ensure that the session are no longer active
130         assertFails(() -> mReporter.assertActiveSession(client.getSessionId()));
131         assertFails(() -> mReporter.assertActiveSession(client2.getSessionId()));
132 
133         // Ensure that future calls to the client fail
134         assertFails(() -> client.notifyAppTargetEvent(null));
135         assertFails(() -> client.notifyLaunchLocationShown(null, null));
136         assertFails(() -> client.registerPredictionUpdates(null, null));
137         assertFails(() -> client.unregisterPredictionUpdates(null));
138         assertFails(() -> client.requestPredictionUpdate());
139         assertFails(() -> client.sortTargets(null, null, null));
140         assertFails(() -> client.requestServiceFeatures(null, null));
141         assertFails(() -> client.destroy());
142     }
143 
144     @Test
testRegisterPredictionUpdatesLifecycle()145     public void testRegisterPredictionUpdatesLifecycle() {
146         AppPredictionContext context = createTestPredictionContext();
147         AppPredictor client = createTestPredictor(context);
148 
149         RequestVerifier cb = new RequestVerifier(mReporter);
150         client.registerPredictionUpdates(Executors.newSingleThreadExecutor(), cb);
151 
152         // Introduce extra delay to ensure AppPredictor#registerPredictionUpdates finishes
153         // execution before calling AppPredictor#requestPredictionUpdate in the following line.
154         // Note that the delay is only needed because of the way the test case is structured.
155         // In production code, AppPredictor#requestPredictionUpdate is invoked in the callback
156         // of AppPredictor#registerPredictionUpdates, which already ensures sequential execution.
157         SystemClock.sleep(500);
158 
159         // Verify some updates
160         assertTrue(cb.requestAndWaitForTargets(createPredictions(),
161                 () -> client.requestPredictionUpdate()));
162         assertTrue(cb.requestAndWaitForTargets(createPredictions(),
163                 () -> client.requestPredictionUpdate()));
164         assertTrue(cb.requestAndWaitForTargets(createPredictions(),
165                 () -> client.requestPredictionUpdate()));
166 
167         client.unregisterPredictionUpdates(cb);
168 
169         // Ensure we don't get updates after the listeners are unregistered
170         assertFalse(cb.requestAndWaitForTargets(createPredictions(),
171                 () -> client.requestPredictionUpdate()));
172 
173         // Clients must be destroyed at end of test.
174         client.destroy();
175     }
176 
177     @Test
testAppTargetEvent()178     public void testAppTargetEvent() {
179         AppPredictionContext context = createTestPredictionContext();
180         AppPredictor client = createTestPredictor(context);
181 
182         List<AppTarget> targets = createPredictions();
183         List<AppTargetEvent> events = new ArrayList<>();
184         for (AppTarget target : targets) {
185             AppTargetEvent event = new AppTargetEvent.Builder(target, TEST_ACTION)
186                     .setLaunchLocation(TEST_LAUNCH_LOCATION)
187                     .build();
188             events.add(event);
189             client.notifyAppTargetEvent(event);
190             mReporter.awaitOnAppTargetEvent();
191         }
192         assertEquals(mReporter.mEvents, events);
193 
194         // Clients must be destroyed at end of test.
195         client.destroy();
196     }
197 
198     @Test
testNotifyLocationShown()199     public void testNotifyLocationShown() {
200         AppPredictionContext context = createTestPredictionContext();
201         AppPredictor client = createTestPredictor(context);
202 
203         List<AppTarget> targets = createPredictions();
204         List<AppTargetId> targetIds = new ArrayList<>();
205         for (AppTarget target : targets) {
206             AppTargetId id = target.getId();
207             targetIds.add(id);
208         }
209         client.notifyLaunchLocationShown(TEST_LAUNCH_LOCATION, targetIds);
210         mReporter.awaitOnLocationShown();
211         assertEquals(mReporter.mLocationsShown, TEST_LAUNCH_LOCATION);
212         assertEquals(mReporter.mLocationsShownTargets, targetIds);
213 
214         // Clients must be destroyed at end of test.
215         client.destroy();
216     }
217 
218     @Test
testSortTargets()219     public void testSortTargets() {
220         AppPredictionContext context = createTestPredictionContext();
221         AppPredictor client = createTestPredictor(context);
222 
223         List<AppTarget> sortedTargets = createPredictions();
224         List<AppTarget> shuffledTargets = new ArrayList<>(sortedTargets);
225         Collections.shuffle(shuffledTargets);
226 
227         // We call sortTargets below with the shuffled targets, ensure that the service receives the
228         // shuffled targets, and return the sorted targets to the RequestVerifier below
229         mReporter.setSortedPredictionsProvider((targets) -> {
230             assertEquals(targets, shuffledTargets);
231             return sortedTargets;
232         });
233         RequestVerifier cb = new RequestVerifier(mReporter);
234         assertTrue(cb.requestAndWaitForTargets(sortedTargets,
235                 () -> client.sortTargets(shuffledTargets,
236                         Executors.newSingleThreadExecutor(), cb)));
237 
238         // Clients must be destroyed at end of test.
239         client.destroy();
240     }
241 
242     @Test
243     @RequiresFlagsEnabled(Flags.FLAG_SERVICE_FEATURES_API)
testRequestServiceFeatures()244     public void testRequestServiceFeatures() {
245         AppPredictionContext context = createTestPredictionContext();
246         AppPredictor client = createTestPredictor(context);
247 
248         Bundle bundle = new Bundle();
249         bundle.putBoolean("TEST", true);
250         mReporter.setServiceFeaturesProvider(() -> bundle);
251         RequestServiceFeaturesVerifier cb = new RequestServiceFeaturesVerifier();
252 
253         assertTrue(cb.requestAndWaitForTargets(bundle,
254                 () -> client.requestServiceFeatures(Executors.newSingleThreadExecutor(), cb)));
255 
256         // Clients must be destroyed at end of test.
257         client.destroy();
258     }
259 
260     @Test
testFailureWithoutPermission()261     public void testFailureWithoutPermission() {
262         setService(null);
263         InstrumentationRegistry.getInstrumentation().getUiAutomation().revokeRuntimePermission(
264                 InstrumentationRegistry.getTargetContext().getPackageName(),
265                 android.Manifest.permission.PACKAGE_USAGE_STATS);
266         assertFails(() -> createTestPredictor(createTestPredictionContext()));
267     }
268 
269     @Test
testSuccessWithPermission()270     public void testSuccessWithPermission() {
271         setService(null);
272         InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
273                 InstrumentationRegistry.getTargetContext().getPackageName(),
274                 android.Manifest.permission.PACKAGE_USAGE_STATS);
275         AppPredictor predictor = createTestPredictor(createTestPredictionContext());
276         predictor.destroy();
277     }
278 
assertFails(Runnable r)279     private void assertFails(Runnable r) {
280         try {
281             r.run();
282         } catch (Exception|Error e) {
283             // Expected failure
284             return;
285         }
286         fail("Expected failure");
287     }
288 
289     /** Creates a random number of targets by increasing id */
createPredictions()290     private List<AppTarget> createPredictions() {
291         List<AppTarget> targets = new ArrayList<>();
292         int n = (int) (Math.random() * 20);
293         for (int i = 0; i < n; i++) {
294             targets.add(new AppTarget.Builder(new AppTargetId(String.valueOf(i)), "test.pkg",
295                     UserHandle.CURRENT)
296                     .setClassName("test.class." + i)
297                     .build());
298         }
299         return targets;
300     }
301 
createTestPredictionContext()302     private AppPredictionContext createTestPredictionContext() {
303         return new AppPredictionContext.Builder(InstrumentationRegistry.getTargetContext())
304                 .setExtras(mPredictionContextExtras)
305                 .setUiSurface(TEST_UI_SURFACE)
306                 .setPredictedTargetCount(TEST_NUM_PREDICTIONS)
307                 .build();
308     }
309 
createTestPredictor(AppPredictionContext context)310     private AppPredictor createTestPredictor(AppPredictionContext context) {
311         Context ctx = InstrumentationRegistry.getTargetContext();
312         AppPredictionManager mgr = (AppPredictionManager) ctx.getSystemService(
313                 APP_PREDICTION_SERVICE);
314         return mgr.createAppPredictionSession(context);
315     }
316 
setService(String service)317     private void setService(String service) {
318         Log.d(TAG, "Setting app prediction service to " + service);
319         int userId = android.os.Process.myUserHandle().getIdentifier();
320         if (service != null) {
321             runShellCommand("cmd app_prediction set temporary-service "
322                     + userId + " " + service + " 12000");
323         } else {
324             runShellCommand("cmd app_prediction set temporary-service " + userId);
325         }
326     }
327 
runShellCommand(String command)328     private void runShellCommand(String command) {
329         Log.d(TAG, "runShellCommand(): " + command);
330         try {
331             SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
332         } catch (Exception e) {
333             throw new RuntimeException("Command '" + command + "' failed: ", e);
334         }
335     }
336 }
337