Merge "Update GameServiceTest to verify screenshot saved" into tm-dev
diff --git a/tests/tests/gameservice/AndroidManifest.xml b/tests/tests/gameservice/AndroidManifest.xml
index a81e64d..936184b 100644
--- a/tests/tests/gameservice/AndroidManifest.xml
+++ b/tests/tests/gameservice/AndroidManifest.xml
@@ -20,6 +20,9 @@
 
     <uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY"/>
     <uses-permission android:name="android.service.games.cts.TEST_START_ACTIVITY"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_MEDIA"/>
 
     <application android:label="CtsGameServiceTestApp">
 
@@ -47,6 +50,7 @@
         </service>
 
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.service.games.testing.GetResultActivity"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/gameservice/src/android/service/games/GameServiceTest.java b/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
index 6d458ae..d95cabe 100644
--- a/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
+++ b/tests/tests/gameservice/src/android/service/games/GameServiceTest.java
@@ -20,30 +20,44 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.app.GameManager;
+import android.app.Instrumentation;
+import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.ImageDecoder;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.provider.MediaStore;
 import android.service.games.testing.ActivityResult;
+import android.service.games.testing.GetResultActivity;
 import android.service.games.testing.IGameServiceTestService;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
+import android.util.Log;
 import android.util.Size;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
-import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.PollingCheck;
@@ -57,7 +71,11 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
 
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
@@ -68,6 +86,8 @@
  */
 @RunWith(AndroidJUnit4.class)
 public final class GameServiceTest {
+    static final String TAG = "GameServiceTest";
+
     private static final String GAME_PACKAGE_NAME = "android.service.games.cts.game";
     private static final String FALSE_POSITIVE_GAME_PACKAGE_NAME =
             "android.service.games.cts.falsepositive";
@@ -83,7 +103,11 @@
     private static final String TOUCH_VERIFIER_PACKAGE_NAME =
             "android.service.games.cts.touchverifier";
 
+    @Parameter(0)
+    public String mVolumeName;
+
     private ServiceConnection mServiceConnection;
+    private ContentResolver mContentResolver;
 
     @Before
     public void setUp() throws Exception {
@@ -106,9 +130,11 @@
 
         GameManager gameManager =
                 getInstrumentation().getContext().getSystemService(GameManager.class);
+
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(gameManager,
                 manager -> manager.setGameServiceProvider(
                         getInstrumentation().getContext().getPackageName()));
+        mContentResolver = getInstrumentation().getContext().getContentResolver();
     }
 
     @After
@@ -378,7 +404,7 @@
     }
 
     @Test
-    public void takeScreenshot_expectedBitmapReturned() throws Exception {
+    public void takeScreenshot_expectedScreenshotSaved() throws Exception {
         assumeGameServiceFeaturePresent();
 
         launchAndWaitForPackage(TAKE_SCREENSHOT_VERIFIER_PACKAGE_NAME);
@@ -386,68 +412,131 @@
         // Make sure that the overlay is shown so that assertions can be made to check that
         // the overlay is excluded from the game screenshot.
         getTestService().showOverlayForFocusedGameSession();
-        Rect overlayBounds = waitForTouchableOverlayBounds();
+        final Rect overlayBounds = waitForTouchableOverlayBounds();
 
-        Bitmap gameScreenshot = getTestService().getBitmapScreenshotForFocusedGameSession();
+        long startTimeSecs = Instant.now().getEpochSecond();
+        final boolean ret = getTestService().takeScreenshotForFocusedGameSession();
 
-        // Make sure a screenshot was taken and has the same dimensions as the device screen.
-        assertNotNull(gameScreenshot);
+        // Make sure a screenshot was taken, saved in media, and has the same dimensions as the
+        // device screen.
+        assertTrue(ret);
+        final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+        final List<Uri> list = new ArrayList<>();
+        try (Cursor cursor = mContentResolver.query(contentUri,
+                new String[]{MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DISPLAY_NAME,
+                        MediaStore.MediaColumns.DATE_ADDED}, null, null,
+                MediaStore.MediaColumns.DATE_ADDED + " DESC")) {
+            while (cursor.moveToNext()) {
+                final long addedTimeSecs = cursor.getLong(2);
+                // try to find the latest screenshot file created within 5s
+                if (addedTimeSecs >= startTimeSecs && addedTimeSecs - startTimeSecs < 5) {
+                    final long id = cursor.getLong(0);
+                    final Uri screenshotUri = ContentUris.withAppendedId(contentUri, id);
+                    final String name = cursor.getString(1);
+                    Log.d(TAG, "Found screenshot with name " + name);
+                    list.add(screenshotUri);
+                    final ImageDecoder.Source source = ImageDecoder.createSource(mContentResolver,
+                            screenshotUri);
+                    // convert the hardware bitmap to a mutable 4-byte bitmap to get/compare pixel
+                    final Bitmap gameScreenshot = ImageDecoder.decodeBitmap(source).copy(
+                            Bitmap.Config.ARGB_8888, true);
 
-        Size screenSize = getScreenSize();
-        assertThat(gameScreenshot.getWidth()).isEqualTo(screenSize.getWidth());
-        assertThat(gameScreenshot.getHeight()).isEqualTo(screenSize.getHeight());
+                    final Size screenSize = getScreenSize();
+                    assertThat(gameScreenshot.getWidth()).isEqualTo(screenSize.getWidth());
+                    assertThat(gameScreenshot.getHeight()).isEqualTo(screenSize.getHeight());
 
-        // The test game is always fullscreen red. It is too expensive to verify that the entire
-        // bitmap is red, so spot check certain areas.
+                    // The test game is always fullscreen red. It is too expensive to verify that
+                    // the entire bitmap is red, so spot check certain areas.
 
-        // 1. Make sure that the overlay is excluded from the screenshot by checking pixels within
-        // the overlay bounds:
+                    // 1. Make sure that the overlay is excluded from the screenshot by checking
+                    // pixels within the overlay bounds:
 
-        // top-left of overlay bounds:
-        assertThat(
-                gameScreenshot.getPixel(overlayBounds.left + 1, overlayBounds.top + 1)).isEqualTo(
-                Color.RED);
-        // bottom-left corner of overlay bounds:
-        assertThat(gameScreenshot.getPixel(overlayBounds.left + 1,
-                overlayBounds.bottom - 1)).isEqualTo(Color.RED);
-        // top-right corner of overlay bounds:
-        assertThat(
-                gameScreenshot.getPixel(overlayBounds.right - 1, overlayBounds.top + 1)).isEqualTo(
-                Color.RED);
-        // bottom-right corner of overlay bounds:
-        assertThat(gameScreenshot.getPixel(overlayBounds.right - 1,
-                overlayBounds.bottom - 1)).isEqualTo(Color.RED);
-        // middle corner of overlay bounds:
-        assertThat(gameScreenshot.getPixel((overlayBounds.left + overlayBounds.right) / 2,
-                (overlayBounds.top + overlayBounds.bottom) / 2)).isEqualTo(Color.RED);
+                    // top-left of overlay bounds:
+                    assertThat(
+                            gameScreenshot.getPixel(overlayBounds.left + 1,
+                                    overlayBounds.top + 1)).isEqualTo(
+                            Color.RED);
+                    // bottom-left corner of overlay bounds:
+                    assertThat(gameScreenshot.getPixel(overlayBounds.left + 1,
+                            overlayBounds.bottom - 1)).isEqualTo(Color.RED);
+                    // top-right corner of overlay bounds:
+                    assertThat(
+                            gameScreenshot.getPixel(overlayBounds.right - 1,
+                                    overlayBounds.top + 1)).isEqualTo(
+                            Color.RED);
+                    // bottom-right corner of overlay bounds:
+                    assertThat(gameScreenshot.getPixel(overlayBounds.right - 1,
+                            overlayBounds.bottom - 1)).isEqualTo(Color.RED);
+                    // middle corner of overlay bounds:
+                    assertThat(
+                            gameScreenshot.getPixel((overlayBounds.left + overlayBounds.right) / 2,
+                                    (overlayBounds.top + overlayBounds.bottom) / 2)).isEqualTo(
+                            Color.RED);
 
-        // 2. Also check some pixels between the edge of the screen and the overlay bounds:
+                    // 2. Also check some pixels between the edge of the screen and the overlay
+                    // bounds:
 
-        // above and to the left of the overlay
-        assertThat(
-                gameScreenshot.getPixel(overlayBounds.left / 2, overlayBounds.top / 2)).isEqualTo(
-                Color.RED);
-        // below and to the left of the overlay
-        assertThat(gameScreenshot.getPixel(overlayBounds.left / 2,
-                (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(Color.RED);
-        // above and to the right of the overlay
-        assertThat(gameScreenshot.getPixel((overlayBounds.left + gameScreenshot.getWidth()) / 2,
-                overlayBounds.top / 2)).isEqualTo(Color.RED);
-        // below and to the right of the overlay
-        assertThat(gameScreenshot.getPixel((overlayBounds.left + gameScreenshot.getWidth()) / 2,
-                (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(Color.RED);
+                    // above and to the left of the overlay
+                    assertThat(
+                            gameScreenshot.getPixel(overlayBounds.left / 2,
+                                    overlayBounds.top / 2)).isEqualTo(
+                            Color.RED);
+                    // below and to the left of the overlay
+                    assertThat(gameScreenshot.getPixel(overlayBounds.left / 2,
+                            (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(
+                            Color.RED);
+                    // above and to the right of the overlay
+                    assertThat(gameScreenshot.getPixel(
+                            (overlayBounds.left + gameScreenshot.getWidth()) / 2,
+                            overlayBounds.top / 2)).isEqualTo(Color.RED);
+                    // below and to the right of the overlay
+                    assertThat(gameScreenshot.getPixel(
+                            (overlayBounds.left + gameScreenshot.getWidth()) / 2,
+                            (overlayBounds.bottom + gameScreenshot.getHeight()) / 2)).isEqualTo(
+                            Color.RED);
 
-        // 3. Finally check some pixels at the corners of the screen:
+                    // 3. Finally check some pixels at the corners of the screen:
 
-        // top-left corner of screen
-        assertThat(gameScreenshot.getPixel(0, 0)).isEqualTo(Color.RED);
-        // bottom-left corner of screen
-        assertThat(gameScreenshot.getPixel(0, gameScreenshot.getHeight() - 1)).isEqualTo(Color.RED);
-        // top-right corner of screen
-        assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1, 0)).isEqualTo(Color.RED);
-        // bottom-right corner of screen
-        assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1,
-                gameScreenshot.getHeight() - 1)).isEqualTo(Color.RED);
+                    // top-left corner of screen
+                    assertThat(gameScreenshot.getPixel(0, 0)).isEqualTo(Color.RED);
+                    // bottom-left corner of screen
+                    assertThat(
+                            gameScreenshot.getPixel(0, gameScreenshot.getHeight() - 1)).isEqualTo(
+                            Color.RED);
+                    // top-right corner of screen
+                    assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1, 0)).isEqualTo(
+                            Color.RED);
+                    // bottom-right corner of screen
+                    assertThat(gameScreenshot.getPixel(gameScreenshot.getWidth() - 1,
+                            gameScreenshot.getHeight() - 1)).isEqualTo(Color.RED);
+                    final PendingIntent pi = MediaStore.createDeleteRequest(mContentResolver,
+                            ImmutableList.of(screenshotUri));
+                    final GetResultActivity.Result result = startIntentWithGrant(pi);
+                    assertEquals(Activity.RESULT_OK, result.resultCode);
+                }
+            }
+        }
+        assertThat(list.size()).isGreaterThan(0);
+    }
+
+    private GetResultActivity.Result startIntentWithGrant(PendingIntent pi) throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        final Intent intent = new Intent(inst.getContext(), GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        final UiDevice device = UiDevice.getInstance(inst);
+        final GetResultActivity activity = (GetResultActivity) inst.startActivitySync(intent);
+        inst.waitForIdleSync();
+        activity.mResult.clear();
+        device.waitForIdle();
+        activity.startIntentSenderForResult(pi.getIntentSender(), 42, null, 0, 0, 0);
+        device.waitForIdle();
+        final UiSelector grant = new UiSelector().textMatches("(?i)Allow");
+        final boolean grantExists = new UiObject(grant).waitForExists(5000);
+        if (grantExists) {
+            device.findObject(grant).click();
+        }
+        return activity.getResult();
     }
 
     private IGameServiceTestService getTestService() {
diff --git a/tests/tests/gameservice/src/android/service/games/GameServiceTestService.java b/tests/tests/gameservice/src/android/service/games/GameServiceTestService.java
index bd0198f..31a4efd 100644
--- a/tests/tests/gameservice/src/android/service/games/GameServiceTestService.java
+++ b/tests/tests/gameservice/src/android/service/games/GameServiceTestService.java
@@ -20,7 +20,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -154,37 +153,36 @@
         }
 
         @Override
-        public Bitmap getBitmapScreenshotForFocusedGameSession() {
+        public boolean takeScreenshotForFocusedGameSession() {
+            boolean result = false;
             TestGameSession focusedGameSession = TestGameSessionService.getFocusedSession();
-            if (focusedGameSession == null) {
-                return null;
+            if (focusedGameSession != null) {
+                CountDownLatch countDownLatch = new CountDownLatch(1);
+                final boolean[] ret = new boolean[1];
+                ScreenshotCallback callback =
+                        new ScreenshotCallback() {
+                            @Override
+                            public void onFailure(int statusCode) {
+                                ret[0] = false;
+                                countDownLatch.countDown();
+                            }
+
+                            @Override
+                            public void onSuccess() {
+                                ret[0] = true;
+                                countDownLatch.countDown();
+                            }
+                        };
+                focusedGameSession.takeScreenshot(Runnable::run, callback);
+                try {
+                    countDownLatch.await(
+                            SCREENSHOT_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    return false;
+                }
+                result = ret[0];
             }
-
-            CountDownLatch countDownLatch = new CountDownLatch(1);
-            Bitmap[] ret = new Bitmap[1];
-            ScreenshotCallback callback =
-                    new ScreenshotCallback() {
-                        @Override
-                        public void onFailure(int statusCode) {
-                            countDownLatch.countDown();
-                        }
-
-                        @Override
-                        public void onSuccess(Bitmap bitmap) {
-                            ret[0] = bitmap;
-                            countDownLatch.countDown();
-                        }
-                    };
-            focusedGameSession.takeScreenshot(Runnable::run, callback);
-
-            try {
-                countDownLatch.await(
-                        SCREENSHOT_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return null;
-            }
-
-            return ret[0];
+            return result;
         }
 
         public OnSystemBarVisibilityChangedInfo getOnSystemBarVisibilityChangedInfo() {
diff --git a/tests/tests/gameservice/src/android/service/games/testing/GetResultActivity.java b/tests/tests/gameservice/src/android/service/games/testing/GetResultActivity.java
new file mode 100644
index 0000000..87f42dd
--- /dev/null
+++ b/tests/tests/gameservice/src/android/service/games/testing/GetResultActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.games.testing;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    public static class Result {
+        public final int requestCode;
+        public final int resultCode;
+        public final Intent data;
+
+        public Result(int requestCode, int resultCode, Intent data) {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.data = data;
+        }
+    }
+    public LinkedBlockingQueue<Result> mResult = new LinkedBlockingQueue<>();
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        mResult.offer(new Result(requestCode, resultCode, data));
+        finish();
+    }
+
+    public Result getResult() {
+        final Result result;
+        try {
+            result = mResult.poll(20, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalStateException("Activity didn't receive a Result in 20 seconds");
+        }
+        return result;
+    }
+}
diff --git a/tests/tests/gameservice/src/android/service/games/testing/IGameServiceTestService.aidl b/tests/tests/gameservice/src/android/service/games/testing/IGameServiceTestService.aidl
index cd227bb..90ed0fe 100644
--- a/tests/tests/gameservice/src/android/service/games/testing/IGameServiceTestService.aidl
+++ b/tests/tests/gameservice/src/android/service/games/testing/IGameServiceTestService.aidl
@@ -16,7 +16,6 @@
 package android.service.games.testing;
 
 import android.content.Intent;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.service.games.testing.ActivityResult;
@@ -43,7 +42,7 @@
 
     void showOverlayForFocusedGameSession();
 
-    Bitmap getBitmapScreenshotForFocusedGameSession();
+    boolean takeScreenshotForFocusedGameSession();
 
     OnSystemBarVisibilityChangedInfo getOnSystemBarVisibilityChangedInfo();