1/*
2 * Copyright (C) 2012 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
17package com.android.camera.activity;
18
19import android.app.Activity;
20import android.app.Instrumentation;
21import android.hardware.Camera;
22import android.hardware.Camera.AutoFocusCallback;
23import android.hardware.Camera.CameraInfo;
24import android.hardware.Camera.Parameters;
25import android.hardware.Camera.PictureCallback;
26import android.hardware.Camera.ShutterCallback;
27import android.test.ActivityInstrumentationTestCase2;
28import android.view.KeyEvent;
29import android.view.MotionEvent;
30import android.view.View;
31
32import com.android.camera.CameraHolder;
33import com.android.camera.CameraManager.CameraProxy;
34import com.android.camera.R;
35import com.android.camera.Util;
36
37import static com.google.testing.littlemock.LittleMock.mock;
38import static com.google.testing.littlemock.LittleMock.doAnswer;
39import static com.google.testing.littlemock.LittleMock.doReturn;
40import static com.google.testing.littlemock.LittleMock.anyObject;
41import com.google.testing.littlemock.AppDataDirGuesser;
42import com.google.testing.littlemock.ArgumentCaptor;
43import com.google.testing.littlemock.Captor;
44import com.google.testing.littlemock.LittleMock;
45import com.google.testing.littlemock.Mock;
46
47import java.io.File;
48import java.io.ByteArrayOutputStream;
49import java.io.InputStream;
50import java.io.IOException;
51import java.util.concurrent.Callable;
52
53
54public class CameraTestCase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
55    protected CameraInfo mCameraInfo[];
56    protected CameraProxy mMockCamera[];
57    protected CameraInfo mOneCameraInfo[];
58    protected CameraProxy mOneMockCamera[];
59    private static Parameters mParameters;
60    private byte[] mBlankJpeg;
61    @Mock private CameraProxy mMockBackCamera;
62    @Mock private CameraProxy mMockFrontCamera;
63    @Captor private ArgumentCaptor<ShutterCallback> mShutterCallback;
64    @Captor private ArgumentCaptor<PictureCallback> mRawPictureCallback;
65    @Captor private ArgumentCaptor<PictureCallback> mJpegPictureCallback;
66    @Captor private ArgumentCaptor<AutoFocusCallback> mAutoFocusCallback;
67    Callable<Object> mAutoFocusCallable = new AutoFocusCallable();
68    Callable<Object> mTakePictureCallable = new TakePictureCallable();
69
70    private class TakePictureCallable implements Callable<Object> {
71        @Override
72        public Object call() throws Exception {
73            Runnable runnable = new Runnable() {
74                @Override
75                public void run() {
76                    readBlankJpeg();
77                    Camera camera = mOneMockCamera[0].getCamera();
78                    mShutterCallback.getValue().onShutter();
79                    mRawPictureCallback.getValue().onPictureTaken(null, camera);
80                    mJpegPictureCallback.getValue().onPictureTaken(mBlankJpeg, camera);
81                }
82            };
83            // Probably need some delay. Make sure shutter callback is called
84            // after onShutterButtonFocus(false).
85            getActivity().findViewById(R.id.gl_root_view).postDelayed(runnable, 50);
86            return null;
87        }
88   }
89
90    private class AutoFocusCallable implements Callable<Object> {
91        @Override
92        public Object call() throws Exception {
93            Runnable runnable = new Runnable() {
94                @Override
95                public void run() {
96                    Camera camera = mOneMockCamera[0].getCamera();
97                    mAutoFocusCallback.getValue().onAutoFocus(true, camera);
98                }
99            };
100            // Need some delay. Otherwise, focus callback will be run before
101            // onShutterButtonClick
102            getActivity().findViewById(R.id.gl_root_view).postDelayed(runnable, 50);
103            return null;
104        }
105   }
106
107    public CameraTestCase(Class<T> activityClass) {
108        super(activityClass);
109    }
110
111    @Override
112    protected void setUp() throws Exception {
113        super.setUp();
114        AppDataDirGuesser.setInstance(new AppDataDirGuesser() {
115            @Override
116            public File guessSuitableDirectoryForGeneratedClasses() {
117                return getInstrumentation().getTargetContext().getCacheDir();
118            }
119        });
120        AppDataDirGuesser.getsInstance().guessSuitableDirectoryForGeneratedClasses();
121        LittleMock.initMocks(this);
122        mCameraInfo = new CameraInfo[2];
123        mCameraInfo[0] = new CameraInfo();
124        mCameraInfo[0].facing = CameraInfo.CAMERA_FACING_BACK;
125        mCameraInfo[1] = new CameraInfo();
126        mCameraInfo[1].facing = CameraInfo.CAMERA_FACING_FRONT;
127        mMockCamera = new CameraProxy[2];
128        mMockCamera[0] = mMockBackCamera;
129        mMockCamera[1] = mMockFrontCamera;
130        doReturn(getParameters()).when(mMockCamera[0]).getParameters();
131        doReturn(getParameters()).when(mMockCamera[1]).getParameters();
132
133        mOneCameraInfo = new CameraInfo[1];
134        mOneCameraInfo[0] = new CameraInfo();
135        mOneCameraInfo[0].facing = CameraInfo.CAMERA_FACING_BACK;
136        mOneMockCamera = new CameraProxy[1];
137        mOneMockCamera[0] = mMockBackCamera;
138        doReturn(getParameters()).when(mOneMockCamera[0]).getParameters();
139
140        // Mock takePicture call.
141        doAnswer(mTakePictureCallable).when(mMockBackCamera).takePicture(
142                mShutterCallback.capture(), mRawPictureCallback.capture(),
143                (PictureCallback) anyObject(), mJpegPictureCallback.capture());
144
145        // Mock autoFocus call.
146        doAnswer(mAutoFocusCallable).when(mMockBackCamera).autoFocus(
147                mAutoFocusCallback.capture());
148    }
149
150    private void readBlankJpeg() {
151        InputStream ins = getActivity().getResources().openRawResource(R.raw.blank);
152        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
153        int size = 0;
154
155        // Read the entire resource into a local byte buffer.
156        byte[] buffer = new byte[1024];
157        try {
158            while((size = ins.read(buffer, 0, 1024)) >= 0){
159                outputStream.write(buffer, 0, size);
160            }
161        } catch (IOException e) {
162            // ignore
163        } finally {
164            Util.closeSilently(ins);
165        }
166        mBlankJpeg = outputStream.toByteArray();
167    }
168
169    @Override
170    protected void tearDown() throws Exception {
171        super.tearDown();
172        CameraHolder.injectMockCamera(null,  null);
173    }
174
175    protected void internalTestFailToConnect() throws Exception {
176        CameraHolder.injectMockCamera(mCameraInfo, null);
177
178        getActivity();
179        Instrumentation inst = getInstrumentation();
180        inst.waitForIdleSync();
181        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); // close dialog
182    }
183
184    protected void internalTestRestoreDefault() throws Exception {
185        CameraHolder.injectMockCamera(mCameraInfo, mMockCamera);
186
187        getActivity();
188        getInstrumentation().waitForIdleSync();
189        performClick(R.id.second_level_indicator);
190        performClick(R.id.other_setting_indicator);
191        performClick(R.id.restore_default);
192        performClick(R.id.rotate_dialog_button1);
193    }
194
195    protected void internalTestOneCamera() throws Exception {
196        CameraHolder.injectMockCamera(mOneCameraInfo, mOneMockCamera);
197
198        getActivity();
199        getInstrumentation().waitForIdleSync();
200        assertViewNotExist(R.id.camera_picker);
201    }
202
203    protected void internalTestSwitchCamera() throws Exception {
204        CameraHolder.injectMockCamera(mCameraInfo, mMockCamera);
205
206        getActivity();
207        getInstrumentation().waitForIdleSync();
208        performClick(R.id.camera_picker);
209    }
210
211    protected void performClick(final int id) {
212        Activity activity = getActivity();
213        getInstrumentation().waitForIdleSync();
214        assertNotNull(activity.findViewById(id));
215        Instrumentation inst = getInstrumentation();
216        inst.runOnMainSync(new Runnable() {
217            @Override
218            public void run() {
219                View v = getActivity().findViewById(id);
220                float x = (v.getLeft() + v.getRight()) / 2;
221                float y = (v.getTop() + v.getBottom()) / 2;
222                MotionEvent down = MotionEvent.obtain(0, 0,
223                        MotionEvent.ACTION_DOWN, x, y, 0, 0, 0, 0, 0, 0, 0);
224                MotionEvent up = MotionEvent.obtain(0, 0,
225                        MotionEvent.ACTION_UP, x, y, 0, 0, 0, 0, 0, 0, 0);
226                View parent = (View) v.getParent();
227                parent.dispatchTouchEvent(down);
228                parent.dispatchTouchEvent(up);
229            }
230        });
231        inst.waitForIdleSync();
232    }
233
234    protected void assertViewNotExist(int id) {
235        Activity activity = getActivity();
236        getInstrumentation().waitForIdleSync();
237        assertNull(activity.findViewById(id));
238    }
239
240    protected void assertViewNotVisible(int id) {
241        Activity activity = getActivity();
242        getInstrumentation().waitForIdleSync();
243        View view = activity.findViewById(id);
244        assertTrue(view.getVisibility() != View.VISIBLE);
245    }
246
247    protected static Parameters getParameters() {
248        synchronized (CameraTestCase.class) {
249            if (mParameters == null) {
250                mParameters = android.hardware.Camera.getEmptyParameters();
251                mParameters.unflatten("preview-format-values=yuv420sp,yuv420p,yuv422i-yuyv,yuv420p;" +
252                        "preview-format=yuv420sp;" +
253                        "preview-size-values=800x480;preview-size=800x480;" +
254                        "picture-size-values=320x240;picture-size=320x240;" +
255                        "jpeg-thumbnail-size-values=320x240,0x0;jpeg-thumbnail-width=320;jpeg-thumbnail-height=240;" +
256                        "jpeg-thumbnail-quality=60;jpeg-quality=95;" +
257                        "preview-frame-rate-values=30,15;preview-frame-rate=30;" +
258                        "focus-mode-values=continuous-video,auto,macro,infinity,continuous-picture;focus-mode=auto;" +
259                        "preview-fps-range-values=(15000,30000);preview-fps-range=15000,30000;" +
260                        "scene-mode-values=auto,action,night;scene-mode=auto;" +
261                        "flash-mode-values=off,on,auto,torch;flash-mode=off;" +
262                        "whitebalance-values=auto,daylight,fluorescent,incandescent;whitebalance=auto;" +
263                        "effect-values=none,mono,sepia;effect=none;" +
264                        "zoom-supported=true;zoom-ratios=100,200,400;max-zoom=2;" +
265                        "picture-format-values=jpeg;picture-format=jpeg;" +
266                        "min-exposure-compensation=-30;max-exposure-compensation=30;" +
267                        "exposure-compensation=0;exposure-compensation-step=0.1;" +
268                        "horizontal-view-angle=40;vertical-view-angle=40;");
269            }
270        }
271        return mParameters;
272    }
273}
274