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.CameraTestDevice;
33import com.android.camera.util.CameraUtil;
34import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
35import com.android.gallery3d.R;
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            CameraUtil.closeSilently(ins);
165        }
166        mBlankJpeg = outputStream.toByteArray();
167    }
168
169    @Override
170    protected void tearDown() throws Exception {
171        super.tearDown();
172        CameraTestDevice.injectMockCamera(null, null);
173    }
174
175    protected void internalTestFailToConnect() throws Exception {
176        CameraTestDevice.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 performClick(final int id) {
185        Activity activity = getActivity();
186        getInstrumentation().waitForIdleSync();
187        assertNotNull(activity.findViewById(id));
188        Instrumentation inst = getInstrumentation();
189        inst.runOnMainSync(new Runnable() {
190            @Override
191            public void run() {
192                View v = getActivity().findViewById(id);
193                float x = (v.getLeft() + v.getRight()) / 2;
194                float y = (v.getTop() + v.getBottom()) / 2;
195                MotionEvent down = MotionEvent.obtain(0, 0,
196                        MotionEvent.ACTION_DOWN, x, y, 0, 0, 0, 0, 0, 0, 0);
197                MotionEvent up = MotionEvent.obtain(0, 0,
198                        MotionEvent.ACTION_UP, x, y, 0, 0, 0, 0, 0, 0, 0);
199                View parent = (View) v.getParent();
200                parent.dispatchTouchEvent(down);
201                parent.dispatchTouchEvent(up);
202            }
203        });
204        inst.waitForIdleSync();
205    }
206
207    protected void assertViewNotExist(int id) {
208        Activity activity = getActivity();
209        getInstrumentation().waitForIdleSync();
210        assertNull(activity.findViewById(id));
211    }
212
213    protected void assertViewNotVisible(int id) {
214        Activity activity = getActivity();
215        getInstrumentation().waitForIdleSync();
216        View view = activity.findViewById(id);
217        assertTrue(view.getVisibility() != View.VISIBLE);
218    }
219
220    protected static Parameters getParameters() {
221        synchronized (CameraTestCase.class) {
222            if (mParameters == null) {
223                mParameters = android.hardware.Camera.getEmptyParameters();
224                mParameters.unflatten("preview-format-values=yuv420sp,yuv420p,yuv422i-yuyv,yuv420p;" +
225                        "preview-format=yuv420sp;" +
226                        "preview-size-values=800x480;preview-size=800x480;" +
227                        "picture-size-values=320x240;picture-size=320x240;" +
228                        "jpeg-thumbnail-size-values=320x240,0x0;jpeg-thumbnail-width=320;jpeg-thumbnail-height=240;" +
229                        "jpeg-thumbnail-quality=60;jpeg-quality=95;" +
230                        "preview-frame-rate-values=30,15;preview-frame-rate=30;" +
231                        "focus-mode-values=continuous-video,auto,macro,infinity,continuous-picture;focus-mode=auto;" +
232                        "preview-fps-range-values=(15000,30000);preview-fps-range=15000,30000;" +
233                        "scene-mode-values=auto,action,night;scene-mode=auto;" +
234                        "flash-mode-values=off,on,auto,torch;flash-mode=off;" +
235                        "whitebalance-values=auto,daylight,fluorescent,incandescent;whitebalance=auto;" +
236                        "effect-values=none,mono,sepia;effect=none;" +
237                        "zoom-supported=true;zoom-ratios=100,200,400;max-zoom=2;" +
238                        "picture-format-values=jpeg;picture-format=jpeg;" +
239                        "min-exposure-compensation=-30;max-exposure-compensation=30;" +
240                        "exposure-compensation=0;exposure-compensation-step=0.1;" +
241                        "horizontal-view-angle=40;vertical-view-angle=40;");
242            }
243        }
244        return mParameters;
245    }
246}
247