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