1caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot/*
2caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * Copyright (C) 2011 The Android Open Source Project
3caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot *
4caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * Licensed under the Apache License, Version 2.0 (the "License");
5caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * you may not use this file except in compliance with the License.
6caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * You may obtain a copy of the License at
7caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot *
8caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot *      http://www.apache.org/licenses/LICENSE-2.0
9caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot *
10caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * Unless required by applicable law or agreed to in writing, software
11caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * distributed under the License is distributed on an "AS IS" BASIS,
12caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * See the License for the specific language governing permissions and
14caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * limitations under the License.
15caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot */
16caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotpackage com.android.systemui.screenshot;
17caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
18caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.graphics.Bitmap;
19caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.graphics.BitmapFactory;
20caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.os.Environment;
21caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.os.FileObserver;
22caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.test.ActivityInstrumentationTestCase2;
23caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.test.suitebuilder.annotation.LargeTest;
24caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.util.Log;
25caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport android.view.KeyEvent;
26caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
27caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotimport java.io.File;
28caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
29caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot/**
30caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot * Functional tests for the global screenshot feature.
31caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot */
32caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot@LargeTest
33caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabotpublic class ScreenshotTest extends ActivityInstrumentationTestCase2<ScreenshotStubActivity> {
34caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
35caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private static final String LOG_TAG = "ScreenshotTest";
36caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private static final int SCREEN_WAIT_TIME_SEC = 5;
37caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
38caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    public ScreenshotTest() {
39caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        super(ScreenshotStubActivity.class);
40caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
41caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
42caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    /**
43caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     * A simple test for screenshots that launches an Activity, injects the key event combo
44caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     * to trigger the screenshot, and verifies the screenshot was taken successfully.
45caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     */
46caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    public void testScreenshot() throws Exception {
47caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        Log.d(LOG_TAG, "starting testScreenshot");
48caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // launch the activity.
49caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        ScreenshotStubActivity activity = getActivity();
50caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        assertNotNull(activity);
51caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
52caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        File screenshotDir = getScreenshotDir();
53caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        NewScreenshotObserver observer = new NewScreenshotObserver(
54caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                screenshotDir.getAbsolutePath());
55caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        observer.startWatching();
56caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        takeScreenshot();
57caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // unlikely, but check if a new screenshot file was already created
58caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        if (observer.getCreatedPath() == null) {
59caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            // wait for screenshot to be created
60caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            synchronized(observer) {
61caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                observer.wait(SCREEN_WAIT_TIME_SEC*1000);
62caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            }
63caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        }
64caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        assertNotNull(String.format("Could not find screenshot after %d seconds",
65caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                SCREEN_WAIT_TIME_SEC), observer.getCreatedPath());
66caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
67caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        File screenshotFile = new File(screenshotDir, observer.getCreatedPath());
68caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        try {
69caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            assertTrue(String.format("Detected new screenshot %s but its not a file",
70caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                    screenshotFile.getName()), screenshotFile.isFile());
71caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            assertTrue(String.format("Detected new screenshot %s but its not an image",
72caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                    screenshotFile.getName()), isValidImage(screenshotFile));
73caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        } finally {
74caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            // delete the file to prevent external storage from filing up
75caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            screenshotFile.delete();
76caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        }
77caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
78caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
79caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private static class NewScreenshotObserver extends FileObserver {
80caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        private String mAddedPath = null;
81caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
82caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        NewScreenshotObserver(String path) {
83caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            super(path, FileObserver.CREATE);
84caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        }
85caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
86caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        synchronized String getCreatedPath() {
87caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            return mAddedPath;
88caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        }
89caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
90caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        @Override
91caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        public void onEvent(int event, String path) {
92caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            Log.d(LOG_TAG, String.format("Detected new file added %s", path));
93caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            synchronized (this) {
94caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                mAddedPath = path;
95caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                notify();
96caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot            }
97caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        }
98caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
99caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
100caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    /**
101caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     * Inject the key sequence to take a screenshot.
102caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     */
103caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private void takeScreenshot() {
104caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN,
105caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                KeyEvent.KEYCODE_POWER));
106caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN,
107caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                KeyEvent.KEYCODE_VOLUME_DOWN));
108caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // the volume down key event will cause the 'volume adjustment' UI to appear in the
109caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // foreground, and steal UI focus
110caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // unfortunately this means the next key event will get directed to the
111caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // 'volume adjustment' UI, instead of this test's activity
112caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // for this reason this test must be signed with platform certificate, to grant this test
113caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // permission to inject key events to another process
114caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_UP,
115caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                KeyEvent.KEYCODE_VOLUME_DOWN));
116caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_UP,
117caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                KeyEvent.KEYCODE_POWER));
118caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
119caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
120caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    /**
121caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     * Get the directory where screenshot images are stored.
122caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     */
123caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private File getScreenshotDir() {
124caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // TODO: get this dir location from a constant
125caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        return new File(Environment.getExternalStorageDirectory(), "Pictures" + File.separator +
126caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot                "Screenshots");
127caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
128caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot
129caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    /**
130caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     * Return true if file is valid image file
131caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot     */
132caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    private boolean isValidImage(File screenshotFile) {
133caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        Bitmap b = BitmapFactory.decodeFile(screenshotFile.getAbsolutePath());
134caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        // TODO: do more checks on image
135caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot        return b != null;
136caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot    }
137caf30a18b52471ebfbc6ae5e853c9a0b9d44905bBrett Chabot}
138