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 
17 package android.service.games;
18 
19 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assume.assumeTrue;
27 
28 import android.app.Activity;
29 import android.app.GameManager;
30 import android.app.Instrumentation;
31 import android.app.PendingIntent;
32 import android.content.ActivityNotFoundException;
33 import android.content.ComponentName;
34 import android.content.ContentResolver;
35 import android.content.ContentUris;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.content.pm.PackageManager;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.Color;
42 import android.graphics.ImageDecoder;
43 import android.graphics.Rect;
44 import android.net.Uri;
45 import android.os.IBinder;
46 import android.os.RemoteException;
47 import android.provider.MediaStore;
48 import android.service.games.testing.ActivityResult;
49 import android.service.games.testing.GameSessionEventInfo;
50 import android.service.games.testing.GetResultActivity;
51 import android.service.games.testing.IGameServiceTestService;
52 import android.support.test.uiautomator.By;
53 import android.support.test.uiautomator.UiDevice;
54 import android.support.test.uiautomator.UiObject;
55 import android.support.test.uiautomator.UiObjectNotFoundException;
56 import android.support.test.uiautomator.UiSelector;
57 import android.support.test.uiautomator.Until;
58 import android.util.Log;
59 import android.util.Size;
60 import android.view.WindowManager;
61 import android.view.WindowMetrics;
62 
63 import androidx.test.InstrumentationRegistry;
64 import androidx.test.filters.FlakyTest;
65 import androidx.test.runner.AndroidJUnit4;
66 
67 import com.android.compatibility.common.util.PollingCheck;
68 import com.android.compatibility.common.util.ShellIdentityUtils;
69 import com.android.compatibility.common.util.ShellUtils;
70 import com.android.compatibility.common.util.UiAutomatorUtils;
71 
72 import com.google.common.collect.ImmutableList;
73 
74 import org.junit.After;
75 import org.junit.Before;
76 import org.junit.Test;
77 import org.junit.runner.RunWith;
78 import org.junit.runners.Parameterized.Parameter;
79 
80 import java.io.ByteArrayOutputStream;
81 import java.io.IOException;
82 import java.time.Instant;
83 import java.util.ArrayList;
84 import java.util.List;
85 import java.util.concurrent.Semaphore;
86 import java.util.concurrent.TimeUnit;
87 import java.util.regex.Matcher;
88 import java.util.regex.Pattern;
89 
90 /**
91  * CTS tests for {@link android.service.games.GameService}.
92  */
93 @FlakyTest(bugId = 263181277)
94 @RunWith(AndroidJUnit4.class)
95 public final class GameServiceTest {
96     static final String TAG = "GameServiceTest";
97 
98     private static final String GAME_PACKAGE_NAME = "android.service.games.cts.game";
99     private static final String FALSE_POSITIVE_GAME_PACKAGE_NAME =
100             "android.service.games.cts.falsepositive";
101     private static final String FINISH_ON_BACK_GAME_PACKAGE_NAME =
102             "android.service.games.cts.finishonbackgame";
103     private static final String NOT_GAME_PACKAGE_NAME = "android.service.games.cts.notgame";
104     private static final String RESTART_GAME_VERIFIER_PACKAGE_NAME =
105             "android.service.games.cts.restartgameverifier";
106     private static final String START_ACTIVITY_VERIFIER_PACKAGE_NAME =
107             "android.service.games.cts.startactivityverifier";
108     private static final String SYSTEM_BAR_VERIFIER_PACKAGE_NAME =
109             "android.service.games.cts.systembarverifier";
110     private static final String TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME =
111             "android.service.games.cts.takescreenshotverifier";
112     private static final String TOUCH_VERIFIER_PACKAGE_NAME =
113             "android.service.games.cts.touchverifier";
114 
115     @Parameter(0)
116     public String mVolumeName;
117 
118     private ServiceConnection mServiceConnection;
119     private ContentResolver mContentResolver;
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         mServiceConnection = new ServiceConnection();
124         assertThat(
125                 getInstrumentation().getContext().bindService(
126                         new Intent("android.service.games.action.TEST_SERVICE").setPackage(
127                                 getInstrumentation().getContext().getPackageName()),
128                         mServiceConnection,
129                         Context.BIND_AUTO_CREATE)).isTrue();
130         mServiceConnection.waitForConnection(10, TimeUnit.SECONDS);
131 
132         getTestService().setGamePackageNames(
133                 ImmutableList.of(
134                         FINISH_ON_BACK_GAME_PACKAGE_NAME,
135                         GAME_PACKAGE_NAME,
136                         RESTART_GAME_VERIFIER_PACKAGE_NAME,
137                         SYSTEM_BAR_VERIFIER_PACKAGE_NAME,
138                         TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME,
139                         TOUCH_VERIFIER_PACKAGE_NAME));
140 
141         GameManager gameManager =
142                 getInstrumentation().getContext().getSystemService(GameManager.class);
143 
144         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(gameManager,
145                 manager -> manager.setGameServiceProvider(
146                         getInstrumentation().getContext().getPackageName()));
147         mContentResolver = getInstrumentation().getContext().getContentResolver();
148 
149         if (gameServiceFeaturePresent()) {
150             waitForGameServiceConnected();
151         }
152     }
153 
154     @After
tearDown()155     public void tearDown() throws Exception {
156         forceStop(GAME_PACKAGE_NAME);
157         forceStop(NOT_GAME_PACKAGE_NAME);
158         forceStop(FALSE_POSITIVE_GAME_PACKAGE_NAME);
159         forceStop(FINISH_ON_BACK_GAME_PACKAGE_NAME);
160         forceStop(RESTART_GAME_VERIFIER_PACKAGE_NAME);
161         forceStop(START_ACTIVITY_VERIFIER_PACKAGE_NAME);
162         forceStop(SYSTEM_BAR_VERIFIER_PACKAGE_NAME);
163         forceStop(TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME);
164         forceStop(TOUCH_VERIFIER_PACKAGE_NAME);
165 
166         GameManager gameManager =
167                 getInstrumentation().getContext().getSystemService(GameManager.class);
168         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(gameManager,
169                 manager -> manager.setGameServiceProvider(""));
170 
171         getTestService().resetState();
172     }
173 
174     @Test
gameService_connectsOnStartup()175     public void gameService_connectsOnStartup() throws Exception {
176         assumeGameServiceFeaturePresent();
177 
178         waitForGameServiceConnected();
179         assertThat(isGameServiceConnected()).isTrue();
180     }
181 
182     @Test
gameService_connectsWhenGameServiceComponentIsEnabled()183     public void gameService_connectsWhenGameServiceComponentIsEnabled() throws Exception {
184         assumeGameServiceFeaturePresent();
185 
186         waitForGameServiceConnected();
187 
188         getTestService().setGameServiceComponentEnabled(false);
189         waitForGameServiceDisconnected();
190 
191         getTestService().setGameServiceComponentEnabled(true);
192         waitForGameServiceConnected();
193     }
194 
195     @Test
gameService_connectsWhenGameSessionServiceComponentIsEnabled()196     public void gameService_connectsWhenGameSessionServiceComponentIsEnabled() throws Exception {
197         assumeGameServiceFeaturePresent();
198 
199         waitForGameServiceConnected();
200 
201         getTestService().setGameSessionServiceComponentEnabled(false);
202         waitForGameServiceDisconnected();
203 
204         getTestService().setGameSessionServiceComponentEnabled(true);
205         waitForGameServiceConnected();
206     }
207 
208     @Test
gameService_startsGameSessionsForGames()209     public void gameService_startsGameSessionsForGames() throws Exception {
210         assumeGameServiceFeaturePresent();
211 
212         launchAndWaitForPackage(NOT_GAME_PACKAGE_NAME);
213         launchAndWaitForPackage(GAME_PACKAGE_NAME);
214         launchAndWaitForPackage(FALSE_POSITIVE_GAME_PACKAGE_NAME);
215 
216         assertThat(getTestService().getActiveSessions()).containsExactly(
217                 GAME_PACKAGE_NAME);
218     }
219 
220     @Test
gameService_multipleGames_startsGameSessionsForGames()221     public void gameService_multipleGames_startsGameSessionsForGames() throws Exception {
222         assumeGameServiceFeaturePresent();
223 
224         launchAndWaitForPackage(NOT_GAME_PACKAGE_NAME);
225         launchAndWaitForPackage(GAME_PACKAGE_NAME);
226         int gameTaskId = getActivityTaskId(
227                 GAME_PACKAGE_NAME,
228                 GAME_PACKAGE_NAME + ".MainActivity");
229         launchAndWaitForPackage(FALSE_POSITIVE_GAME_PACKAGE_NAME);
230         launchAndWaitForPackage(RESTART_GAME_VERIFIER_PACKAGE_NAME);
231         int restartGameTaskId = getActivityTaskId(
232                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
233                 RESTART_GAME_VERIFIER_PACKAGE_NAME + ".MainActivity");
234 
235         assertThat(getTestService().getActiveSessions()).containsExactly(
236                 GAME_PACKAGE_NAME, RESTART_GAME_VERIFIER_PACKAGE_NAME);
237 
238         List<GameSessionEventInfo> gameSessionEventHistory =
239                 getTestService().getGameSessionEventHistory();
240         assertThat(gameSessionEventHistory)
241                 .containsExactly(
242                         GameSessionEventInfo.create(
243                                 GAME_PACKAGE_NAME,
244                                 gameTaskId,
245                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED),
246                         GameSessionEventInfo.create(
247                                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
248                                 restartGameTaskId,
249                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED))
250                 .inOrder();
251     }
252 
253     @Test
gameService_multipleGamesIncludingStops_startsGameSessionsForGames()254     public void gameService_multipleGamesIncludingStops_startsGameSessionsForGames()
255             throws Exception {
256         assumeGameServiceFeaturePresent();
257 
258         launchAndWaitForPackage(NOT_GAME_PACKAGE_NAME);
259         launchAndWaitForPackage(GAME_PACKAGE_NAME);
260         int gameTaskId = getActivityTaskId(
261                 GAME_PACKAGE_NAME,
262                 GAME_PACKAGE_NAME + ".MainActivity");
263         launchAndWaitForPackage(FALSE_POSITIVE_GAME_PACKAGE_NAME);
264         launchAndWaitForPackage(RESTART_GAME_VERIFIER_PACKAGE_NAME);
265         int restartGameTaskId = getActivityTaskId(
266                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
267                 RESTART_GAME_VERIFIER_PACKAGE_NAME + ".MainActivity");
268         forceStop(GAME_PACKAGE_NAME);
269 
270         assertThat(getTestService().getActiveSessions()).containsExactly(
271                 RESTART_GAME_VERIFIER_PACKAGE_NAME);
272 
273         List<GameSessionEventInfo> gameSessionEventHistory =
274                 getTestService().getGameSessionEventHistory();
275         assertThat(gameSessionEventHistory)
276                 .containsExactly(
277                         GameSessionEventInfo.create(
278                                 GAME_PACKAGE_NAME,
279                                 gameTaskId,
280                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED),
281                         GameSessionEventInfo.create(
282                                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
283                                 restartGameTaskId,
284                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED),
285                         GameSessionEventInfo.create(
286                                 GAME_PACKAGE_NAME,
287                                 gameTaskId,
288                                 GameSessionEventInfo.GAME_SESSION_EVENT_DESTROYED))
289                 .inOrder();
290     }
291 
292     @Test
getTaskId_returnsTaskIdOfGame()293     public void getTaskId_returnsTaskIdOfGame() throws Exception {
294         assumeGameServiceFeaturePresent();
295 
296         launchAndWaitForPackage(GAME_PACKAGE_NAME);
297 
298         int taskId = getTestService().getFocusedTaskId();
299 
300         assertThat(taskId).isEqualTo(
301                 getActivityTaskId(GAME_PACKAGE_NAME, GAME_PACKAGE_NAME + ".MainActivity"));
302     }
303 
304     @Test
setTaskOverlayView_addsViewsToOverlay()305     public void setTaskOverlayView_addsViewsToOverlay() throws Exception {
306         assumeGameServiceFeaturePresent();
307 
308         launchAndWaitForPackage(GAME_PACKAGE_NAME);
309 
310         waitForTouchableOverlayBounds();
311 
312         assertThat(UiAutomatorUtils.getUiDevice().findObject(
313                 By.text("Overlay was rendered on: " + GAME_PACKAGE_NAME))).isNotNull();
314     }
315 
316     @Test
setTaskOverlayView_passesTouchesOutsideOverlayToGame()317     public void setTaskOverlayView_passesTouchesOutsideOverlayToGame() throws Exception {
318         assumeGameServiceFeaturePresent();
319 
320         launchAndWaitForPackage(TOUCH_VERIFIER_PACKAGE_NAME);
321 
322         Rect touchableBounds = waitForTouchableOverlayBounds();
323         UiAutomatorUtils.getUiDevice().click(touchableBounds.centerX(), touchableBounds.centerY());
324 
325         UiAutomatorUtils.waitFindObject(
326                 By.res(TOUCH_VERIFIER_PACKAGE_NAME, "times_clicked").text("0"));
327 
328         UiAutomatorUtils.getUiDevice().click(touchableBounds.centerX(), touchableBounds.top - 30);
329         UiAutomatorUtils.waitFindObject(
330                 By.res(TOUCH_VERIFIER_PACKAGE_NAME, "times_clicked").text("1"));
331 
332         UiAutomatorUtils.getUiDevice()
333                 .click(touchableBounds.centerX(), touchableBounds.bottom + 30);
334         UiAutomatorUtils.waitFindObject(
335                 By.res(TOUCH_VERIFIER_PACKAGE_NAME, "times_clicked").text("2"));
336 
337         UiAutomatorUtils.getUiDevice().click(touchableBounds.left - 30, touchableBounds.centerY());
338         UiAutomatorUtils.waitFindObject(
339                 By.res(TOUCH_VERIFIER_PACKAGE_NAME, "times_clicked").text("3"));
340 
341         UiAutomatorUtils.getUiDevice().click(touchableBounds.right + 30, touchableBounds.centerY());
342         UiAutomatorUtils.waitFindObject(
343                 By.res(TOUCH_VERIFIER_PACKAGE_NAME, "times_clicked").text("4"));
344     }
345 
346     @Test
onTransientSystemBarVisibilityChanged_nonTransient_doesNotDispatchShow()347     public void onTransientSystemBarVisibilityChanged_nonTransient_doesNotDispatchShow()
348             throws Exception {
349         assumeGameServiceFeaturePresent();
350 
351         launchAndWaitForPackage(SYSTEM_BAR_VERIFIER_PACKAGE_NAME);
352 
353         UiAutomatorUtils.getUiDevice().findObject(
354                         By.text("Show system bars permanently")
355                                 .pkg(SYSTEM_BAR_VERIFIER_PACKAGE_NAME))
356                 .click();
357 
358         assertThat(
359                 getTestService().getOnSystemBarVisibilityChangedInfo().getTimesShown())
360                 .isEqualTo(0);
361     }
362 
363     @Test
onTransientSystemBarVisibilityFromRevealGestureChanged_dispatchesHideEvent()364     public void onTransientSystemBarVisibilityFromRevealGestureChanged_dispatchesHideEvent()
365             throws Exception {
366         assumeGameServiceFeaturePresent();
367 
368         launchAndWaitForPackage(GAME_PACKAGE_NAME);
369         swipeFromTopEdgeToShowSystemBars();
370 
371         assertThat(
372                 getTestService().getOnSystemBarVisibilityChangedInfo().getTimesShown())
373                 .isEqualTo(1);
374 
375         PollingCheck.waitFor(
376                 () -> {
377                     try {
378                         return getTestService().getOnSystemBarVisibilityChangedInfo()
379                                 .getTimesHidden() > 0;
380                     } catch (RemoteException e) {
381                         return false;
382                     }
383                 });
384         assertThat(
385                 getTestService().getOnSystemBarVisibilityChangedInfo().getTimesHidden())
386                 .isEqualTo(1);
387     }
388 
389     @Test
startActivityForResult_startsActivityAndReceivesResultWithData()390     public void startActivityForResult_startsActivityAndReceivesResultWithData() throws Exception {
391         assumeGameServiceFeaturePresent();
392 
393         launchAndWaitForPackage(GAME_PACKAGE_NAME);
394 
395         StartActivityVerifierPage.launch(getTestService());
396 
397         StartActivityVerifierPage.setResultCode(10);
398         StartActivityVerifierPage.setResultData("foobar");
399         StartActivityVerifierPage.clickSendResultButton();
400 
401         ActivityResult result = getTestService().getLastActivityResult();
402 
403         assertThat(result.getGameSessionPackageName()).isEqualTo(GAME_PACKAGE_NAME);
404         assertThat(result.getSuccess().getResultCode()).isEqualTo(10);
405         String resultData = StartActivityVerifierPage.getResultData(result.getSuccess().getData());
406         assertThat(resultData).isEqualTo("foobar");
407     }
408 
409     @Test
startActivityForResult_startsActivityAndReceivesResultWithNoData()410     public void startActivityForResult_startsActivityAndReceivesResultWithNoData()
411             throws Exception {
412         assumeGameServiceFeaturePresent();
413 
414         launchAndWaitForPackage(GAME_PACKAGE_NAME);
415 
416         StartActivityVerifierPage.launch(getTestService());
417 
418         StartActivityVerifierPage.setResultCode(10);
419         StartActivityVerifierPage.clickSendResultButton();
420 
421         ActivityResult result = getTestService().getLastActivityResult();
422 
423         assertThat(result.getGameSessionPackageName()).isEqualTo(GAME_PACKAGE_NAME);
424         assertThat(result.getSuccess().getResultCode()).isEqualTo(10);
425         assertThat(result.getSuccess().getData()).isNull();
426     }
427 
428     @Test
startActivityForResult_cannotStartBlockedActivities()429     public void startActivityForResult_cannotStartBlockedActivities() throws Exception {
430         assumeGameServiceFeaturePresent();
431 
432         launchAndWaitForPackage(GAME_PACKAGE_NAME);
433 
434         getTestService().startGameSessionActivity(
435                 new Intent("android.service.games.cts.startactivityverifier.START_BLOCKED"), null);
436 
437         ActivityResult result = getTestService().getLastActivityResult();
438 
439         assertThat(result.getGameSessionPackageName()).isEqualTo(GAME_PACKAGE_NAME);
440         assertThat(result.getFailure().getClazz()).isEqualTo(SecurityException.class);
441     }
442 
443     @Test
startActivityForResult_propagatesActivityNotFoundException()444     public void startActivityForResult_propagatesActivityNotFoundException() throws Exception {
445         assumeGameServiceFeaturePresent();
446 
447         launchAndWaitForPackage(GAME_PACKAGE_NAME);
448 
449         getTestService().startGameSessionActivity(new Intent("NO_ACTION"), null);
450 
451         ActivityResult result = getTestService().getLastActivityResult();
452 
453         assertThat(result.getGameSessionPackageName()).isEqualTo(GAME_PACKAGE_NAME);
454         assertThat(result.getFailure().getClazz()).isEqualTo(ActivityNotFoundException.class);
455     }
456     @Test
restartGame_noPermission()457     public void restartGame_noPermission() throws Exception {
458         assumeGameServiceFeaturePresent();
459         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
460         clearCache(RESTART_GAME_VERIFIER_PACKAGE_NAME);
461         RestartGameVerifierPage.launch();
462 
463         RestartGameVerifierPage.assertTimesStarted(1);
464         RestartGameVerifierPage.assertHasSavedInstanceState(false);
465 
466         getTestService().restartFocusedGameSession();
467 
468         RestartGameVerifierPage.assertTimesStarted(1);
469         RestartGameVerifierPage.assertHasSavedInstanceState(false);
470     }
471 
472     @Test
restartGame_gameAppIsRestarted()473     public void restartGame_gameAppIsRestarted() throws Exception {
474         assumeGameServiceFeaturePresent();
475 
476         clearCache(RESTART_GAME_VERIFIER_PACKAGE_NAME);
477         RestartGameVerifierPage.launch();
478 
479         RestartGameVerifierPage.assertTimesStarted(1);
480         RestartGameVerifierPage.assertHasSavedInstanceState(false);
481 
482         getTestService().restartFocusedGameSession();
483 
484         RestartGameVerifierPage.assertTimesStarted(2);
485         RestartGameVerifierPage.assertHasSavedInstanceState(true);
486 
487         getTestService().restartFocusedGameSession();
488 
489         RestartGameVerifierPage.assertTimesStarted(3);
490         RestartGameVerifierPage.assertHasSavedInstanceState(true);
491     }
492 
493     @Test
restartGame_gameSessionIsPersisted()494     public void restartGame_gameSessionIsPersisted() throws Exception {
495         assumeGameServiceFeaturePresent();
496 
497         clearCache(RESTART_GAME_VERIFIER_PACKAGE_NAME);
498         RestartGameVerifierPage.launch();
499 
500         int gameTaskId = getActivityTaskId(
501                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
502                 RESTART_GAME_VERIFIER_PACKAGE_NAME + ".MainActivity");
503 
504         RestartGameVerifierPage.assertTimesStarted(1);
505         RestartGameVerifierPage.assertHasSavedInstanceState(false);
506 
507         getTestService().restartFocusedGameSession();
508 
509         RestartGameVerifierPage.assertTimesStarted(2);
510         RestartGameVerifierPage.assertHasSavedInstanceState(true);
511 
512         List<GameSessionEventInfo> gameSessionEventHistory =
513                 getTestService().getGameSessionEventHistory();
514 
515         assertThat(gameSessionEventHistory)
516                 .containsExactly(
517                         GameSessionEventInfo.create(
518                                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
519                                 gameTaskId,
520                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED))
521                 .inOrder();
522     }
523 
524     @Test
restartGame_withNonGameActivityAbove_gameSessionIsPersisted()525     public void restartGame_withNonGameActivityAbove_gameSessionIsPersisted() throws Exception {
526         assumeGameServiceFeaturePresent();
527 
528         clearCache(RESTART_GAME_VERIFIER_PACKAGE_NAME);
529         RestartGameVerifierPage.launch();
530 
531         int gameTaskId = getActivityTaskId(
532                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
533                 RESTART_GAME_VERIFIER_PACKAGE_NAME + ".MainActivity");
534 
535         RestartGameVerifierPage.assertTimesStarted(1);
536         RestartGameVerifierPage.assertHasSavedInstanceState(false);
537 
538         StartActivityVerifierPage.launch(getTestService());
539         StartActivityVerifierPage.setResultCode(0x1337);
540         StartActivityVerifierPage.setResultData("hello mom!");
541 
542         getTestService().restartFocusedGameSession();
543 
544         StartActivityVerifierPage.assertLaunched();
545         StartActivityVerifierPage.clickSendResultButton();
546 
547         RestartGameVerifierPage.assertTimesStarted(2);
548         RestartGameVerifierPage.assertHasSavedInstanceState(true);
549 
550         List<GameSessionEventInfo> gameSessionEventHistory =
551                 getTestService().getGameSessionEventHistory();
552 
553         assertThat(gameSessionEventHistory)
554                 .containsExactly(
555                         GameSessionEventInfo.create(
556                                 RESTART_GAME_VERIFIER_PACKAGE_NAME,
557                                 gameTaskId,
558                                 GameSessionEventInfo.GAME_SESSION_EVENT_CREATED))
559                 .inOrder();
560 
561         ActivityResult result = getTestService().getLastActivityResult();
562 
563         assertThat(result.getGameSessionPackageName()).isEqualTo(
564                 RESTART_GAME_VERIFIER_PACKAGE_NAME);
565         assertThat(result.getSuccess().getResultCode()).isEqualTo(0x1337);
566         String resultData = StartActivityVerifierPage.getResultData(result.getSuccess().getData());
567         assertThat(resultData).isEqualTo("hello mom!");
568     }
569 
570     @Test
gamePutInBackgroundAndRestoredViaRecentsUi_gameSessionRestarted()571     public void gamePutInBackgroundAndRestoredViaRecentsUi_gameSessionRestarted() throws Exception {
572         assumeGameServiceFeaturePresent();
573 
574         // The test game finishes its activity when it is put in the background by a press of the
575         // back button.
576         launchAndWaitForPackage(FINISH_ON_BACK_GAME_PACKAGE_NAME);
577 
578         assertThat(getTestService().getActiveSessions()).containsExactly(
579                 FINISH_ON_BACK_GAME_PACKAGE_NAME);
580 
581         // Close the game by pressing the back button.
582         UiAutomatorUtils.getUiDevice().pressBack();
583         UiAutomatorUtils.getUiDevice().waitForIdle();
584 
585         // With the game closed the game session should be destroyed
586         PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(20),
587                 () -> {
588                     try {
589                         return getTestService().getActiveSessions().isEmpty();
590                     } catch (RemoteException e) {
591                         throw new RuntimeException(e);
592                     }
593                 },
594                 "Timed out waiting for game session to be destroyed.");
595 
596         // Restore the game via the Recents UI
597         UiAutomatorUtils.getUiDevice().pressRecentApps();
598         UiAutomatorUtils.getUiDevice().waitForIdle();
599 
600         logWindowHierarchy("after pressing recent apps");
601 
602         // Don't specify the full name of the test game "CtsGameServiceFinishOnBackGame" because it
603         // may be ellipsized in the Recents UI.
604         UiAutomatorUtils.waitFindObject(By.textStartsWith("CtsGameService")).click();
605 
606         logWindowHierarchy("after clicking on recent app");
607 
608         // There should again be an active game session for the restored game.
609         PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(20),
610                 () -> {
611                     try {
612                         return getTestService().getActiveSessions().size() == 1
613                                 && getTestService().getActiveSessions().get(0).equals(
614                                 FINISH_ON_BACK_GAME_PACKAGE_NAME);
615                     } catch (RemoteException e) {
616                         throw new RuntimeException(e);
617                     }
618                 },
619                 "Timed out waiting for game session to be re-created.");
620     }
621 
622     @Test
takeScreenshot_noPermission()623     public void takeScreenshot_noPermission() throws Exception {
624         assumeGameServiceFeaturePresent();
625         launchAndWaitForPackage(TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME);
626         waitForTouchableOverlayBounds();
627         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
628         final boolean ret = getTestService().takeScreenshotForFocusedGameSession();
629         assertFalse(ret);
630     }
631 
632     @Test
takeScreenshot_expectedScreenshotSaved()633     public void takeScreenshot_expectedScreenshotSaved() throws Exception {
634         assumeGameServiceFeaturePresent();
635 
636         launchAndWaitForPackage(TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME);
637 
638         // Make sure that the overlay is shown so that assertions can be made to check that
639         // the overlay is excluded from the game screenshot.
640         final Rect overlayBounds = waitForTouchableOverlayBounds();
641 
642         long startTimeSecs = Instant.now().getEpochSecond();
643         final boolean ret = getTestService().takeScreenshotForFocusedGameSession();
644 
645         // Make sure a screenshot was taken, saved in media, and has the same dimensions as the
646         // device screen.
647         assertTrue(ret);
648 
649         List<Uri> screenshotUris = PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(5),
650                 () -> getScreenshotsFromLastFiveSeconds(startTimeSecs), uris -> !uris.isEmpty());
651         for (Uri screenshotUri : screenshotUris) {
652             final ImageDecoder.Source source = ImageDecoder.createSource(mContentResolver,
653                     screenshotUri);
654             // convert the hardware bitmap to a mutable 4-byte bitmap to get/compare pixel
655             final Bitmap gameScreenshot = ImageDecoder.decodeBitmap(source).copy(
656                     Bitmap.Config.ARGB_8888, true);
657 
658             final Size screenSize = getScreenSize();
659             assertThat(gameScreenshot.getWidth()).isEqualTo(screenSize.getWidth());
660             assertThat(gameScreenshot.getHeight()).isEqualTo(screenSize.getHeight());
661 
662             // The test game is always fullscreen red. It is too expensive to verify that
663             // the entire bitmap is red, so spot check certain areas.
664 
665             // 1. Make sure that the overlay is excluded from the screenshot by checking
666             // pixels within the overlay bounds:
667 
668             // top-left of overlay bounds:
669             assertThat(
670                     gameScreenshot.getPixel(overlayBounds.left + 1,
671                             overlayBounds.top + 1)).isEqualTo(
672                     Color.RED);
673             // bottom-left corner of overlay bounds:
674             assertThat(gameScreenshot.getPixel(overlayBounds.left + 1,
675                     overlayBounds.bottom - 1)).isEqualTo(Color.RED);
676             // top-right corner of overlay bounds:
677             assertThat(
678                     gameScreenshot.getPixel(overlayBounds.right - 1,
679                             overlayBounds.top + 1)).isEqualTo(
680                     Color.RED);
681             // bottom-right corner of overlay bounds:
682             assertThat(gameScreenshot.getPixel(overlayBounds.right - 1,
683                     overlayBounds.bottom - 1)).isEqualTo(Color.RED);
684             // middle corner of overlay bounds:
685             assertThat(
686                     gameScreenshot.getPixel((overlayBounds.left + overlayBounds.right) / 2,
687                             (overlayBounds.top + overlayBounds.bottom) / 2)).isEqualTo(
688                     Color.RED);
689 
690             // 2. Also check some pixels between the edge of the screen and the overlay
691             // bounds:
692 
693             // above and to the left of the overlay
694             assertThat(
695                     gameScreenshot.getPixel(overlayBounds.left / 2,
696                             overlayBounds.top / 2)).isEqualTo(
697                     Color.RED);
698             // below and to the left of the overlay
699             assertThat(gameScreenshot.getPixel(overlayBounds.left / 2,
700                     (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(
701                     Color.RED);
702             // above and to the right of the overlay
703             assertThat(gameScreenshot.getPixel(
704                     (overlayBounds.left + gameScreenshot.getWidth()) / 2,
705                     overlayBounds.top / 2)).isEqualTo(Color.RED);
706             // below and to the right of the overlay
707             assertThat(gameScreenshot.getPixel(
708                     (overlayBounds.left + gameScreenshot.getWidth()) / 2,
709                     (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(
710                     Color.RED);
711 
712             // 3. Finally check some pixels at the corners of the screen:
713 
714             // top-left corner of screen
715             assertThat(gameScreenshot.getPixel(0, 0)).isEqualTo(Color.RED);
716             // bottom-left corner of screen
717             assertThat(
718                     gameScreenshot.getPixel(0, gameScreenshot.getHeight() - 1)).isEqualTo(
719                     Color.RED);
720             // top-right corner of screen
721             assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1, 0)).isEqualTo(
722                     Color.RED);
723             // bottom-right corner of screen
724             assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1,
725                     gameScreenshot.getHeight() - 1)).isEqualTo(Color.RED);
726             final PendingIntent pi = MediaStore.createDeleteRequest(mContentResolver,
727                     ImmutableList.of(screenshotUri));
728             final GetResultActivity.Result result = startIntentWithGrant(pi);
729             assertEquals(Activity.RESULT_OK, result.resultCode);
730         }
731     }
732 
getScreenshotsFromLastFiveSeconds(long startTimeSecs)733     private List<Uri> getScreenshotsFromLastFiveSeconds(long startTimeSecs) {
734         final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
735         final List<Uri> screenshotUris = new ArrayList<>();
736         try (Cursor cursor = mContentResolver.query(contentUri,
737                 new String[]{MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME,
738                         MediaStore.MediaColumns.DATE_ADDED}, null, null,
739                 MediaStore.MediaColumns.DATE_ADDED + " DESC")) {
740             while (cursor.moveToNext()) {
741                 final long addedTimeSecs = cursor.getLong(2);
742                 // try to find the latest screenshot file created within 5s
743                 if (addedTimeSecs >= startTimeSecs && addedTimeSecs - startTimeSecs < 5) {
744                     final long id = cursor.getLong(0);
745                     final Uri screenshotUri = ContentUris.withAppendedId(contentUri, id);
746                     final String name = cursor.getString(1);
747                     Log.d(TAG, "Found screenshot with name " + name);
748                     screenshotUris.add(screenshotUri);
749                 }
750             }
751         }
752         return screenshotUris;
753     }
754 
startIntentWithGrant(PendingIntent pi)755     private GetResultActivity.Result startIntentWithGrant(PendingIntent pi) throws Exception {
756         final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
757         final Intent intent = new Intent(inst.getContext(), GetResultActivity.class);
758         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
759 
760         final UiDevice device = UiDevice.getInstance(inst);
761         final GetResultActivity activity = (GetResultActivity) inst.startActivitySync(intent);
762         inst.waitForIdleSync();
763         activity.mResult.clear();
764         device.waitForIdle();
765         activity.startIntentSenderForResult(pi.getIntentSender(), 42, null, 0, 0, 0);
766         device.waitForIdle();
767         final UiSelector grant = new UiSelector().textMatches("(?i)Allow");
768         final boolean grantExists = new UiObject(grant).waitForExists(5000);
769         if (grantExists) {
770             device.findObject(grant).click();
771         }
772         return activity.getResult();
773     }
774 
getTestService()775     private IGameServiceTestService getTestService() {
776         return mServiceConnection.mService;
777     }
778 
assumeGameServiceFeaturePresent()779     private static void assumeGameServiceFeaturePresent() {
780         assumeTrue(gameServiceFeaturePresent());
781     }
782 
gameServiceFeaturePresent()783     private static boolean gameServiceFeaturePresent() {
784         return getInstrumentation().getContext().getPackageManager().hasSystemFeature(
785                 PackageManager.FEATURE_GAME_SERVICE);
786     }
787 
launchAndWaitForPackage(String packageName)788     private static void launchAndWaitForPackage(String packageName) throws Exception {
789         PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
790         getInstrumentation().getContext().startActivity(
791                 packageManager.getLaunchIntentForPackage(packageName));
792         UiAutomatorUtils.waitFindObject(By.pkg(packageName).depth(0));
793     }
794 
setText(String resourcePackage, String resourceId, String text)795     private static void setText(String resourcePackage, String resourceId, String text)
796             throws Exception {
797         UiAutomatorUtils.waitFindObject(By.res(resourcePackage, resourceId))
798                 .setText(text);
799     }
800 
click(String resourcePackage, String resourceId)801     private static void click(String resourcePackage, String resourceId) throws Exception {
802         UiAutomatorUtils.waitFindObject(By.res(resourcePackage, resourceId))
803                 .click();
804     }
805 
swipeFromTopEdgeToShowSystemBars()806     private static void swipeFromTopEdgeToShowSystemBars() {
807         UiDevice uiDevice = UiAutomatorUtils.getUiDevice();
808         uiDevice.swipe(
809                 uiDevice.getDisplayWidth() / 2, 20,
810                 uiDevice.getDisplayWidth() / 2, uiDevice.getDisplayHeight() / 2,
811                 10);
812     }
813 
waitForGameServiceConnected()814     private void waitForGameServiceConnected() {
815         PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(40), () -> isGameServiceConnected(),
816                 "Timed out waiting for game service to connect");
817     }
818 
waitForGameServiceDisconnected()819     private void waitForGameServiceDisconnected() {
820         PollingCheck.waitFor(TimeUnit.SECONDS.toMillis(5), () -> !isGameServiceConnected(),
821                 "Timed out waiting for game service to disconnect");
822     }
823 
isGameServiceConnected()824     private boolean isGameServiceConnected() {
825         try {
826             return getTestService().isGameServiceConnected();
827         } catch (RemoteException e) {
828             throw new RuntimeException(e);
829         }
830     }
831 
waitForTouchableOverlayBounds()832     private Rect waitForTouchableOverlayBounds() {
833         return PollingCheck.waitFor(
834                 TimeUnit.SECONDS.toMillis(5),
835                 () -> {
836                     try {
837                         return getTestService().getTouchableOverlayBounds();
838                     } catch (RemoteException e) {
839                         throw new RuntimeException(e);
840                     }
841                 },
842                 bounds -> bounds != null && !bounds.isEmpty());
843     }
844 
845     private void assertOverlayDoesNotAppear() {
846         assertThat(waitForTouchableOverlayBounds().isEmpty()).isTrue();
847     }
848 
849     private Rect waitForOverlayToDisappear() {
850         return PollingCheck.waitFor(
851                 TimeUnit.SECONDS.toMillis(20),
852                 () -> {
853                     try {
854                         return getTestService().getTouchableOverlayBounds();
855                     } catch (RemoteException e) {
856                         throw new RuntimeException(e);
857                     }
858                 },
859                 Rect::isEmpty);
860     }
861 
862     private static void forceStop(String packageName) {
863         ShellUtils.runShellCommand("am force-stop %s", packageName);
864         UiAutomatorUtils.getUiDevice().wait(Until.gone(By.pkg(packageName).depth(0)),
865                 TimeUnit.SECONDS.toMillis(20));
866     }
867 
868     private static void clearCache(String packageName) {
869         ShellUtils.runShellCommand("pm clear %s", packageName);
870     }
871 
872     private static int getActivityTaskId(String packageName, String componentName) {
873         final String output = ShellUtils.runShellCommand("am stack list");
874         final Pattern pattern = Pattern.compile(
875                 String.format(".*taskId=([0-9]+): %s/%s.*", packageName, componentName));
876 
877         for (String line : output.split("\\n")) {
878             Matcher matcher = pattern.matcher(line);
879             if (matcher.matches()) {
880                 String taskId = matcher.group(1);
881                 return Integer.parseInt(taskId);
882             }
883         }
884 
885         return -1;
886     }
887 
888     private static class RestartGameVerifierPage {
889         private RestartGameVerifierPage() {
890         }
891 
892         public static void launch() throws Exception {
893             launchAndWaitForPackage(RESTART_GAME_VERIFIER_PACKAGE_NAME);
894         }
895 
896         public static void assertTimesStarted(int times) throws UiObjectNotFoundException {
897             UiAutomatorUtils.waitFindObject(
898                     By.res(RESTART_GAME_VERIFIER_PACKAGE_NAME, "times_started").text(
899                             String.valueOf(times)));
900         }
901 
902         public static void assertHasSavedInstanceState(boolean hasSavedInstanceState)
903                 throws UiObjectNotFoundException {
904             UiAutomatorUtils.waitFindObject(
905                     By.res(RESTART_GAME_VERIFIER_PACKAGE_NAME, "has_saved_instance_state").text(
906                             String.valueOf(hasSavedInstanceState)));
907         }
908     }
909 
910     private static class StartActivityVerifierPage {
911 
912         public static void launch(IGameServiceTestService testService) throws Exception {
913             testService.startGameSessionActivity(
914                     new Intent("android.service.games.cts.startactivityverifier.START"), null);
915         }
916 
917         public static void assertLaunched() throws UiObjectNotFoundException {
918             UiAutomatorUtils.waitFindObject(By.pkg(START_ACTIVITY_VERIFIER_PACKAGE_NAME).depth(0));
919         }
920 
921         public static void setResultCode(int resultCode) throws Exception {
922             setText(START_ACTIVITY_VERIFIER_PACKAGE_NAME, "result_code_edit_text",
923                     String.valueOf(resultCode));
924         }
925 
926         public static void setResultData(String resultData) throws Exception {
927             setText(START_ACTIVITY_VERIFIER_PACKAGE_NAME, "result_data_edit_text", resultData);
928         }
929 
930         public static void clickSendResultButton() throws Exception {
931             click(START_ACTIVITY_VERIFIER_PACKAGE_NAME, "send_result_button");
932         }
933 
934         public static String getResultData(Intent data) {
935             return data.getStringExtra("data");
936         }
937     }
938 
939     private static Size getScreenSize() {
940         WindowManager wm =
941                 (WindowManager)
942                         InstrumentationRegistry.getInstrumentation()
943                                 .getContext()
944                                 .getSystemService(Context.WINDOW_SERVICE);
945         WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
946         Rect windowBounds = windowMetrics.getBounds();
947         return new Size(windowBounds.width(), windowBounds.height());
948     }
949 
950     private void logWindowHierarchy(String state) {
951         try {
952             ByteArrayOutputStream os = new ByteArrayOutputStream();
953             UiAutomatorUtils.getUiDevice().dumpWindowHierarchy(os);
954 
955             Log.d(TAG, "Window hierarchy: " + state);
956             for (String line : os.toString("UTF-8").split("\n")) {
957                 Log.d(TAG, line);
958             }
959         } catch (IOException e) {
960             throw new RuntimeException(e);
961         }
962     }
963 
964     private static final class ServiceConnection implements android.content.ServiceConnection {
965         private final Semaphore mSemaphore = new Semaphore(0);
966         private IGameServiceTestService mService;
967 
968         @Override
969         public void onServiceConnected(ComponentName name, IBinder service) {
970             mService = IGameServiceTestService.Stub.asInterface(service);
971             mSemaphore.release();
972         }
973 
974         @Override
975         public void onServiceDisconnected(ComponentName name) {
976             mService = null;
977         }
978 
979         public void waitForConnection(int timeout, TimeUnit timeUnit) throws Exception {
980             assertThat(mSemaphore.tryAcquire(timeout, timeUnit)).isTrue();
981         }
982     }
983 }
984