Doris Liu | ffa6237 | 2013-01-31 19:03:48 -0800 | [diff] [blame] | 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 | |
| 17 | package com.android.camera.activity; |
| 18 | |
| 19 | import android.app.Activity; |
| 20 | import android.app.Instrumentation; |
| 21 | import android.hardware.Camera; |
| 22 | import android.hardware.Camera.AutoFocusCallback; |
| 23 | import android.hardware.Camera.CameraInfo; |
| 24 | import android.hardware.Camera.Parameters; |
| 25 | import android.hardware.Camera.PictureCallback; |
| 26 | import android.hardware.Camera.ShutterCallback; |
| 27 | import android.test.ActivityInstrumentationTestCase2; |
| 28 | import android.view.KeyEvent; |
| 29 | import android.view.MotionEvent; |
| 30 | import android.view.View; |
| 31 | |
| 32 | import com.android.camera.CameraHolder; |
Angus Kong | 20fad24 | 2013-11-11 18:23:46 -0800 | [diff] [blame^] | 33 | import com.android.camera.app.CameraManager.CameraProxy; |
Angus Kong | b50b5cb | 2013-08-09 14:55:20 -0700 | [diff] [blame] | 34 | import com.android.camera.util.CameraUtil; |
John Reck | 26ac261 | 2013-02-15 16:08:26 -0800 | [diff] [blame] | 35 | import com.android.gallery3d.R; |
Doris Liu | ffa6237 | 2013-01-31 19:03:48 -0800 | [diff] [blame] | 36 | |
| 37 | import static com.google.testing.littlemock.LittleMock.mock; |
| 38 | import static com.google.testing.littlemock.LittleMock.doAnswer; |
| 39 | import static com.google.testing.littlemock.LittleMock.doReturn; |
| 40 | import static com.google.testing.littlemock.LittleMock.anyObject; |
| 41 | import com.google.testing.littlemock.AppDataDirGuesser; |
| 42 | import com.google.testing.littlemock.ArgumentCaptor; |
| 43 | import com.google.testing.littlemock.Captor; |
| 44 | import com.google.testing.littlemock.LittleMock; |
| 45 | import com.google.testing.littlemock.Mock; |
| 46 | |
| 47 | import java.io.File; |
| 48 | import java.io.ByteArrayOutputStream; |
| 49 | import java.io.InputStream; |
| 50 | import java.io.IOException; |
| 51 | import java.util.concurrent.Callable; |
| 52 | |
| 53 | |
| 54 | public 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 { |
Angus Kong | b50b5cb | 2013-08-09 14:55:20 -0700 | [diff] [blame] | 164 | CameraUtil.closeSilently(ins); |
Doris Liu | ffa6237 | 2013-01-31 19:03:48 -0800 | [diff] [blame] | 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 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 | } |