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