├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── twoeightnine │ │ └── root │ │ └── camera │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── twoeightnine │ │ │ └── root │ │ │ └── camera │ │ │ ├── AutoFitTextureView.java │ │ │ ├── Camera2BasicFragment.java │ │ │ └── CameraActivity.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_action_info.png │ │ ├── ic_launcher.png │ │ └── tile.9.png │ │ ├── drawable-mdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ ├── ic_action_info.png │ │ └── ic_launcher.png │ │ ├── drawable │ │ └── task.jpg │ │ ├── layout-land │ │ └── fragment_camera2_basic.xml │ │ ├── layout │ │ ├── activity_camera.xml │ │ └── fragment_camera2_basic.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-sw600dp │ │ ├── template-dimens.xml │ │ └── template-styles.xml │ │ ├── values-v11 │ │ └── template-styles.xml │ │ ├── values-v21 │ │ ├── base-colors.xml │ │ └── base-template-styles.xml │ │ └── values │ │ ├── base-strings.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ ├── template-dimens.xml │ │ └── template-styles.xml │ └── test │ └── java │ └── com │ └── twoeightnine │ └── root │ └── camera │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidSpyCamera 2 | 3 | This app shows how you can quietly take photos of app user via front camera. After opening it shows an image with puzzle (its role is to attract attention) while fromt camera takes photos every 2 secs and saves it into ~/Android/data/{appId}/files. 4 | 5 | Basically advantage: app targets on API 22, so you WILL NOT SEE A RUNTIME PERMISSION TO ACCESS CAMERA. 6 | 7 | If you want to protect your privacy: 8 | - trust no one 9 | - close your front camera 10 | 11 | If you want to break someone's privacy: 12 | - add this code to your app 13 | - use encryption to hide fact of capturing 14 | - send all encrypted photos next time user turn on the Internet 15 | 16 | I DO NOT PROPAGANDIZE THIS. 17 | I just want you to know how it is easy to break your privacy! 18 | 19 | This app is based on Camera2Basic repo from Google. 20 | 21 | # Privacy is an illusion 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'android-apt' 3 | 4 | android { 5 | compileSdkVersion 22 6 | buildToolsVersion "26.0.1" 7 | defaultConfig { 8 | applicationId "com.twoeightnine.root.camera" 9 | minSdkVersion 21 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 26 | exclude group: 'com.android.support', module: 'support-annotations' 27 | }) 28 | compile 'com.android.support:appcompat-v7:22.+' 29 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 30 | testCompile 'junit:junit:4.12' 31 | 32 | compile "com.jakewharton:butterknife:8.5.1" 33 | apt "com.jakewharton:butterknife-compiler:8.5.1" 34 | } 35 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /root/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twoeightnine/root/camera/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.twoeightnine.root.camera; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.twoeightnine.root.camera", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/twoeightnine/root/camera/AutoFitTextureView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 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.twoeightnine.root.camera; 18 | 19 | import android.content.Context; 20 | import android.util.AttributeSet; 21 | import android.view.TextureView; 22 | 23 | /** 24 | * A {@link TextureView} that can be adjusted to a specified aspect ratio. 25 | */ 26 | public class AutoFitTextureView extends TextureView { 27 | 28 | private int mRatioWidth = 0; 29 | private int mRatioHeight = 0; 30 | 31 | public AutoFitTextureView(Context context) { 32 | this(context, null); 33 | } 34 | 35 | public AutoFitTextureView(Context context, AttributeSet attrs) { 36 | this(context, attrs, 0); 37 | } 38 | 39 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { 40 | super(context, attrs, defStyle); 41 | } 42 | 43 | /** 44 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 45 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that 46 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. 47 | * 48 | * @param width Relative horizontal size 49 | * @param height Relative vertical size 50 | */ 51 | public void setAspectRatio(int width, int height) { 52 | if (width < 0 || height < 0) { 53 | throw new IllegalArgumentException("Size cannot be negative."); 54 | } 55 | mRatioWidth = width; 56 | mRatioHeight = height; 57 | requestLayout(); 58 | } 59 | 60 | @Override 61 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 62 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 63 | int width = MeasureSpec.getSize(widthMeasureSpec); 64 | int height = MeasureSpec.getSize(heightMeasureSpec); 65 | if (0 == mRatioWidth || 0 == mRatioHeight) { 66 | setMeasuredDimension(width, height); 67 | } else { 68 | if (width < height * mRatioWidth / mRatioHeight) { 69 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); 70 | } else { 71 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); 72 | } 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/twoeightnine/root/camera/Camera2BasicFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 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.twoeightnine.root.camera; 18 | 19 | import android.app.Activity; 20 | import android.app.AlertDialog; 21 | import android.app.Dialog; 22 | import android.content.Context; 23 | import android.content.DialogInterface; 24 | import android.content.res.Configuration; 25 | import android.graphics.ImageFormat; 26 | import android.graphics.Matrix; 27 | import android.graphics.Point; 28 | import android.graphics.RectF; 29 | import android.graphics.SurfaceTexture; 30 | import android.hardware.camera2.CameraAccessException; 31 | import android.hardware.camera2.CameraCaptureSession; 32 | import android.hardware.camera2.CameraCharacteristics; 33 | import android.hardware.camera2.CameraDevice; 34 | import android.hardware.camera2.CameraManager; 35 | import android.hardware.camera2.CameraMetadata; 36 | import android.hardware.camera2.CaptureRequest; 37 | import android.hardware.camera2.CaptureResult; 38 | import android.hardware.camera2.TotalCaptureResult; 39 | import android.hardware.camera2.params.StreamConfigurationMap; 40 | import android.media.Image; 41 | import android.media.ImageReader; 42 | import android.os.Bundle; 43 | import android.os.CountDownTimer; 44 | import android.os.Handler; 45 | import android.os.HandlerThread; 46 | import android.support.annotation.NonNull; 47 | import android.support.annotation.UiThread; 48 | import android.support.v4.app.DialogFragment; 49 | import android.support.v4.app.Fragment; 50 | import android.util.Log; 51 | import android.util.Size; 52 | import android.util.SparseIntArray; 53 | import android.view.LayoutInflater; 54 | import android.view.Surface; 55 | import android.view.TextureView; 56 | import android.view.View; 57 | import android.view.ViewGroup; 58 | import android.widget.TextView; 59 | import android.widget.Toast; 60 | 61 | import java.io.File; 62 | import java.io.FileOutputStream; 63 | import java.io.IOException; 64 | import java.nio.ByteBuffer; 65 | import java.util.ArrayList; 66 | import java.util.Arrays; 67 | import java.util.Collections; 68 | import java.util.Comparator; 69 | import java.util.List; 70 | import java.util.concurrent.Semaphore; 71 | import java.util.concurrent.TimeUnit; 72 | 73 | public class Camera2BasicFragment extends Fragment 74 | implements View.OnClickListener { 75 | 76 | /** 77 | * Conversion from screen rotation to JPEG orientation. 78 | */ 79 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); 80 | private static final int REQUEST_CAMERA_PERMISSION = 1; 81 | private static final String FRAGMENT_DIALOG = "dialog"; 82 | 83 | static { 84 | ORIENTATIONS.append(Surface.ROTATION_0, 90); 85 | ORIENTATIONS.append(Surface.ROTATION_90, 0); 86 | ORIENTATIONS.append(Surface.ROTATION_180, 270); 87 | ORIENTATIONS.append(Surface.ROTATION_270, 180); 88 | } 89 | 90 | /** 91 | * Tag for the {@link Log}. 92 | */ 93 | private static final String TAG = "Camera2BasicFragment"; 94 | 95 | /** 96 | * Camera state: Showing camera preview. 97 | */ 98 | private static final int STATE_PREVIEW = 0; 99 | 100 | /** 101 | * Camera state: Waiting for the focus to be locked. 102 | */ 103 | private static final int STATE_WAITING_LOCK = 1; 104 | 105 | /** 106 | * Camera state: Waiting for the exposure to be precapture state. 107 | */ 108 | private static final int STATE_WAITING_PRECAPTURE = 2; 109 | 110 | /** 111 | * Camera state: Waiting for the exposure state to be something other than precapture. 112 | */ 113 | private static final int STATE_WAITING_NON_PRECAPTURE = 3; 114 | 115 | /** 116 | * Camera state: Picture was taken. 117 | */ 118 | private static final int STATE_PICTURE_TAKEN = 4; 119 | 120 | /** 121 | * Max preview width that is guaranteed by Camera2 API 122 | */ 123 | private static final int MAX_PREVIEW_WIDTH = 1920; 124 | 125 | /** 126 | * Max preview height that is guaranteed by Camera2 API 127 | */ 128 | private static final int MAX_PREVIEW_HEIGHT = 1080; 129 | 130 | /** 131 | * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 132 | * {@link TextureView}. 133 | */ 134 | private final TextureView.SurfaceTextureListener mSurfaceTextureListener 135 | = new TextureView.SurfaceTextureListener() { 136 | 137 | @Override 138 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { 139 | openCamera(width, height); 140 | } 141 | 142 | @Override 143 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { 144 | configureTransform(width, height); 145 | } 146 | 147 | @Override 148 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { 149 | return true; 150 | } 151 | 152 | @Override 153 | public void onSurfaceTextureUpdated(SurfaceTexture texture) { 154 | } 155 | 156 | }; 157 | 158 | /** 159 | * ID of the current {@link CameraDevice}. 160 | */ 161 | private String mCameraId; 162 | 163 | /** 164 | * An {@link AutoFitTextureView} for camera preview. 165 | */ 166 | private AutoFitTextureView mTextureView; 167 | 168 | /** 169 | * A {@link CameraCaptureSession } for camera preview. 170 | */ 171 | private CameraCaptureSession mCaptureSession; 172 | 173 | /** 174 | * A reference to the opened {@link CameraDevice}. 175 | */ 176 | private CameraDevice mCameraDevice; 177 | 178 | /** 179 | * The {@link android.util.Size} of camera preview. 180 | */ 181 | private Size mPreviewSize; 182 | 183 | private int counter = 0; 184 | private TextView tvCounter; 185 | 186 | /** 187 | * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state. 188 | */ 189 | private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 190 | 191 | @Override 192 | public void onOpened(@NonNull CameraDevice cameraDevice) { 193 | // This method is called when the camera is opened. We start camera preview here. 194 | mCameraOpenCloseLock.release(); 195 | mCameraDevice = cameraDevice; 196 | createCameraPreviewSession(); 197 | } 198 | 199 | @Override 200 | public void onDisconnected(@NonNull CameraDevice cameraDevice) { 201 | mCameraOpenCloseLock.release(); 202 | cameraDevice.close(); 203 | mCameraDevice = null; 204 | } 205 | 206 | @Override 207 | public void onError(@NonNull CameraDevice cameraDevice, int error) { 208 | mCameraOpenCloseLock.release(); 209 | cameraDevice.close(); 210 | mCameraDevice = null; 211 | Activity activity = getActivity(); 212 | if (null != activity) { 213 | activity.finish(); 214 | } 215 | } 216 | 217 | }; 218 | 219 | /** 220 | * An additional thread for running tasks that shouldn't block the UI. 221 | */ 222 | private HandlerThread mBackgroundThread; 223 | 224 | /** 225 | * A {@link Handler} for running tasks in the background. 226 | */ 227 | private Handler mBackgroundHandler; 228 | 229 | /** 230 | * An {@link ImageReader} that handles still image capture. 231 | */ 232 | private ImageReader mImageReader; 233 | 234 | /** 235 | * This is the output file for our picture. 236 | */ 237 | private File mFile; 238 | 239 | /** 240 | * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a 241 | * still image is ready to be saved. 242 | */ 243 | private final ImageReader.OnImageAvailableListener mOnImageAvailableListener 244 | = new ImageReader.OnImageAvailableListener() { 245 | 246 | @Override 247 | public void onImageAvailable(ImageReader reader) { 248 | mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile, getActivity())); 249 | } 250 | 251 | }; 252 | 253 | /** 254 | * {@link CaptureRequest.Builder} for the camera preview 255 | */ 256 | private CaptureRequest.Builder mPreviewRequestBuilder; 257 | 258 | /** 259 | * {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder} 260 | */ 261 | private CaptureRequest mPreviewRequest; 262 | 263 | /** 264 | * The current state of camera state for taking pictures. 265 | * 266 | * @see #mCaptureCallback 267 | */ 268 | private int mState = STATE_PREVIEW; 269 | 270 | /** 271 | * A {@link Semaphore} to prevent the app from exiting before closing the camera. 272 | */ 273 | private Semaphore mCameraOpenCloseLock = new Semaphore(1); 274 | 275 | /** 276 | * Whether the current camera device supports Flash or not. 277 | */ 278 | private boolean mFlashSupported; 279 | 280 | /** 281 | * Orientation of the camera sensor 282 | */ 283 | private int mSensorOrientation; 284 | 285 | /** 286 | * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture. 287 | */ 288 | private CameraCaptureSession.CaptureCallback mCaptureCallback 289 | = new CameraCaptureSession.CaptureCallback() { 290 | 291 | private void process(CaptureResult result) { 292 | switch (mState) { 293 | case STATE_PREVIEW: { 294 | // We have nothing to do when the camera preview is working normally. 295 | break; 296 | } 297 | case STATE_WAITING_LOCK: { 298 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); 299 | if (afState == null) { 300 | captureStillPicture(); 301 | } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || 302 | CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { 303 | // CONTROL_AE_STATE can be null on some devices 304 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 305 | if (aeState == null || 306 | aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 307 | mState = STATE_PICTURE_TAKEN; 308 | captureStillPicture(); 309 | } else { 310 | runPrecaptureSequence(); 311 | } 312 | } else { 313 | captureStillPicture(); 314 | } 315 | break; 316 | } 317 | case STATE_WAITING_PRECAPTURE: { 318 | // CONTROL_AE_STATE can be null on some devices 319 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 320 | if (aeState == null || 321 | aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 322 | aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { 323 | mState = STATE_WAITING_NON_PRECAPTURE; 324 | } 325 | break; 326 | } 327 | case STATE_WAITING_NON_PRECAPTURE: { 328 | // CONTROL_AE_STATE can be null on some devices 329 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); 330 | if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 331 | mState = STATE_PICTURE_TAKEN; 332 | captureStillPicture(); 333 | } 334 | break; 335 | } 336 | } 337 | } 338 | 339 | @Override 340 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 341 | @NonNull CaptureRequest request, 342 | @NonNull CaptureResult partialResult) { 343 | process(partialResult); 344 | } 345 | 346 | @Override 347 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 348 | @NonNull CaptureRequest request, 349 | @NonNull TotalCaptureResult result) { 350 | process(result); 351 | } 352 | 353 | }; 354 | 355 | /** 356 | * Shows a {@link Toast} on the UI thread. 357 | * 358 | * @param text The message to show 359 | */ 360 | private void showToast(final String text) { 361 | final Activity activity = getActivity(); 362 | if (activity != null) { 363 | activity.runOnUiThread(new Runnable() { 364 | @Override 365 | public void run() { 366 | Toast.makeText(activity, text, Toast.LENGTH_SHORT).show(); 367 | } 368 | }); 369 | } 370 | } 371 | 372 | /** 373 | * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that 374 | * is at least as large as the respective texture view size, and that is at most as large as the 375 | * respective max size, and whose aspect ratio matches with the specified value. If such size 376 | * doesn't exist, choose the largest one that is at most as large as the respective max size, 377 | * and whose aspect ratio matches with the specified value. 378 | * 379 | * @param choices The list of sizes that the camera supports for the intended output 380 | * class 381 | * @param textureViewWidth The width of the texture view relative to sensor coordinate 382 | * @param textureViewHeight The height of the texture view relative to sensor coordinate 383 | * @param maxWidth The maximum width that can be chosen 384 | * @param maxHeight The maximum height that can be chosen 385 | * @param aspectRatio The aspect ratio 386 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough 387 | */ 388 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth, 389 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) { 390 | 391 | // Collect the supported resolutions that are at least as big as the preview Surface 392 | List bigEnough = new ArrayList<>(); 393 | // Collect the supported resolutions that are smaller than the preview Surface 394 | List notBigEnough = new ArrayList<>(); 395 | int w = aspectRatio.getWidth(); 396 | int h = aspectRatio.getHeight(); 397 | for (Size option : choices) { 398 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight && 399 | option.getHeight() == option.getWidth() * h / w) { 400 | if (option.getWidth() >= textureViewWidth && 401 | option.getHeight() >= textureViewHeight) { 402 | bigEnough.add(option); 403 | } else { 404 | notBigEnough.add(option); 405 | } 406 | } 407 | } 408 | 409 | // Pick the smallest of those big enough. If there is no one big enough, pick the 410 | // largest of those not big enough. 411 | if (bigEnough.size() > 0) { 412 | return Collections.min(bigEnough, new CompareSizesByArea()); 413 | } else if (notBigEnough.size() > 0) { 414 | return Collections.max(notBigEnough, new CompareSizesByArea()); 415 | } else { 416 | Log.e(TAG, "Couldn't find any suitable preview size"); 417 | return choices[0]; 418 | } 419 | } 420 | 421 | public static Camera2BasicFragment newInstance() { 422 | return new Camera2BasicFragment(); 423 | } 424 | 425 | @Override 426 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 427 | Bundle savedInstanceState) { 428 | return inflater.inflate(R.layout.fragment_camera2_basic, container, false); 429 | } 430 | 431 | @Override 432 | public void onViewCreated(final View view, Bundle savedInstanceState) { 433 | final View picture = view.findViewById(R.id.picture); 434 | picture.setOnClickListener(this); 435 | view.findViewById(R.id.info).setOnClickListener(this); 436 | mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 437 | tvCounter = (TextView) view.findViewById(R.id.tvCounter); 438 | 439 | final Runnable runnable = new Object() { 440 | final Runnable runnable = new Runnable() { 441 | @Override 442 | public void run() { 443 | new CountDownTimer(2000, 2000) { 444 | @Override 445 | public void onTick(long millisUntilFinished) {} 446 | 447 | @Override 448 | public void onFinish() { 449 | takePicture(); 450 | runnable.run(); 451 | } 452 | }.start(); 453 | } 454 | }; 455 | }.runnable; 456 | new CountDownTimer(2000, 2000) { 457 | @Override 458 | public void onTick(long millisUntilFinished) {} 459 | 460 | @Override 461 | public void onFinish() { 462 | runnable.run(); 463 | } 464 | }.start(); 465 | } 466 | 467 | @Override 468 | public void onActivityCreated(Bundle savedInstanceState) { 469 | super.onActivityCreated(savedInstanceState); 470 | // mFileDir = getActivity().getExternalFilesDir(null); 471 | mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg"); 472 | } 473 | 474 | @Override 475 | public void onResume() { 476 | super.onResume(); 477 | startBackgroundThread(); 478 | 479 | // When the screen is turned off and turned back on, the SurfaceTexture is already 480 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open 481 | // a camera and start preview from here (otherwise, we wait until the surface is ready in 482 | // the SurfaceTextureListener). 483 | if (mTextureView.isAvailable()) { 484 | openCamera(mTextureView.getWidth(), mTextureView.getHeight()); 485 | } else { 486 | mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 487 | } 488 | } 489 | 490 | @Override 491 | public void onPause() { 492 | closeCamera(); 493 | stopBackgroundThread(); 494 | super.onPause(); 495 | } 496 | 497 | /** 498 | * Sets up member variables related to camera. 499 | * 500 | * @param width The width of available size for camera preview 501 | * @param height The height of available size for camera preview 502 | */ 503 | @SuppressWarnings("SuspiciousNameCombination") 504 | private void setUpCameraOutputs(int width, int height) { 505 | Activity activity = getActivity(); 506 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 507 | try { 508 | String[] cameraIds = manager.getCameraIdList(); 509 | // for (String s : cameraIds) { 510 | // Log.i("qwer", "" + s); 511 | // } 512 | for (String cameraId : cameraIds) { 513 | Log.i("qwer", "cameId=" + cameraId); 514 | CameraCharacteristics characteristics 515 | = manager.getCameraCharacteristics(cameraId); 516 | 517 | // We don't use a front facing camera in this sample. 518 | Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); 519 | Log.i("qwer", "fac=" + facing); 520 | if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) { 521 | Log.i("qwer", "cont fac " + facing); 522 | continue; 523 | } 524 | 525 | StreamConfigurationMap map = characteristics.get( 526 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 527 | if (map == null) { 528 | Log.i("qwer", "map null"); 529 | continue; 530 | } 531 | 532 | // For still image captures, we use the largest available size. 533 | Size largest = Collections.max( 534 | Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), 535 | new CompareSizesByArea()); 536 | mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), 537 | ImageFormat.JPEG, /*maxImages*/2); 538 | mImageReader.setOnImageAvailableListener( 539 | mOnImageAvailableListener, mBackgroundHandler); 540 | 541 | // Find out if we need to swap dimension to get the preview size relative to sensor 542 | // coordinate. 543 | int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 544 | //noinspection ConstantConditions 545 | mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 546 | boolean swappedDimensions = false; 547 | switch (displayRotation) { 548 | case Surface.ROTATION_0: 549 | case Surface.ROTATION_180: 550 | if (mSensorOrientation == 90 || mSensorOrientation == 270) { 551 | swappedDimensions = true; 552 | } 553 | break; 554 | case Surface.ROTATION_90: 555 | case Surface.ROTATION_270: 556 | if (mSensorOrientation == 0 || mSensorOrientation == 180) { 557 | swappedDimensions = true; 558 | } 559 | break; 560 | default: 561 | Log.e(TAG, "Display rotation is invalid: " + displayRotation); 562 | } 563 | 564 | Point displaySize = new Point(); 565 | activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 566 | int rotatedPreviewWidth = width; 567 | int rotatedPreviewHeight = height; 568 | int maxPreviewWidth = displaySize.x; 569 | int maxPreviewHeight = displaySize.y; 570 | 571 | if (swappedDimensions) { 572 | rotatedPreviewWidth = height; 573 | rotatedPreviewHeight = width; 574 | maxPreviewWidth = displaySize.y; 575 | maxPreviewHeight = displaySize.x; 576 | } 577 | 578 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) { 579 | maxPreviewWidth = MAX_PREVIEW_WIDTH; 580 | } 581 | 582 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) { 583 | maxPreviewHeight = MAX_PREVIEW_HEIGHT; 584 | } 585 | 586 | // Danger, W.R.! Attempting to use too large a preview size could exceed the camera 587 | // bus' bandwidth limitation, resulting in gorgeous previews but the storage of 588 | // garbage capture data. 589 | mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 590 | rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, 591 | maxPreviewHeight, largest); 592 | 593 | // We fit the aspect ratio of TextureView to the size of preview we picked. 594 | int orientation = getResources().getConfiguration().orientation; 595 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 596 | mTextureView.setAspectRatio( 597 | mPreviewSize.getWidth(), mPreviewSize.getHeight()); 598 | } else { 599 | mTextureView.setAspectRatio( 600 | mPreviewSize.getHeight(), mPreviewSize.getWidth()); 601 | } 602 | 603 | // Check if the flash is supported. 604 | Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE); 605 | mFlashSupported = available == null ? false : available; 606 | 607 | mCameraId = cameraId; 608 | Log.i("qwer", "cam id " + mCameraId); 609 | return; 610 | } 611 | } catch (CameraAccessException e) { 612 | e.printStackTrace(); 613 | } catch (NullPointerException e) { 614 | // Currently an NPE is thrown when the Camera2API is used but not supported on the 615 | // device this code runs. 616 | ErrorDialog.newInstance(getString(R.string.camera_error)) 617 | .show(getChildFragmentManager(), FRAGMENT_DIALOG); 618 | } 619 | } 620 | 621 | /** 622 | * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}. 623 | */ 624 | private void openCamera(int width, int height) { 625 | setUpCameraOutputs(width, height); 626 | configureTransform(width, height); 627 | Activity activity = getActivity(); 628 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 629 | try { 630 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 631 | throw new RuntimeException("Time out waiting to lock camera opening."); 632 | } 633 | Log.i("qwer", "camId cr " + mCameraId); 634 | manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler); 635 | } catch (CameraAccessException e) { 636 | e.printStackTrace(); 637 | } catch (InterruptedException e) { 638 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e); 639 | } 640 | } 641 | 642 | /** 643 | * Closes the current {@link CameraDevice}. 644 | */ 645 | private void closeCamera() { 646 | try { 647 | mCameraOpenCloseLock.acquire(); 648 | if (null != mCaptureSession) { 649 | mCaptureSession.close(); 650 | mCaptureSession = null; 651 | } 652 | if (null != mCameraDevice) { 653 | mCameraDevice.close(); 654 | mCameraDevice = null; 655 | } 656 | if (null != mImageReader) { 657 | mImageReader.close(); 658 | mImageReader = null; 659 | } 660 | } catch (InterruptedException e) { 661 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e); 662 | } finally { 663 | mCameraOpenCloseLock.release(); 664 | } 665 | } 666 | 667 | /** 668 | * Starts a background thread and its {@link Handler}. 669 | */ 670 | private void startBackgroundThread() { 671 | mBackgroundThread = new HandlerThread("CameraBackground"); 672 | mBackgroundThread.start(); 673 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 674 | } 675 | 676 | /** 677 | * Stops the background thread and its {@link Handler}. 678 | */ 679 | private void stopBackgroundThread() { 680 | mBackgroundThread.quitSafely(); 681 | try { 682 | mBackgroundThread.join(); 683 | mBackgroundThread = null; 684 | mBackgroundHandler = null; 685 | } catch (InterruptedException e) { 686 | e.printStackTrace(); 687 | } 688 | } 689 | 690 | /** 691 | * Creates a new {@link CameraCaptureSession} for camera preview. 692 | */ 693 | private void createCameraPreviewSession() { 694 | try { 695 | SurfaceTexture texture = mTextureView.getSurfaceTexture(); 696 | assert texture != null; 697 | 698 | // We configure the size of default buffer to be the size of camera preview we want. 699 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 700 | 701 | // This is the output Surface we need to start preview. 702 | Surface surface = new Surface(texture); 703 | 704 | // We set up a CaptureRequest.Builder with the output Surface. 705 | mPreviewRequestBuilder 706 | = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 707 | mPreviewRequestBuilder.addTarget(surface); 708 | 709 | // Here, we create a CameraCaptureSession for camera preview. 710 | mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), 711 | new CameraCaptureSession.StateCallback() { 712 | 713 | @Override 714 | public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 715 | // The camera is already closed 716 | if (null == mCameraDevice) { 717 | return; 718 | } 719 | 720 | // When the session is ready, we start displaying the preview. 721 | mCaptureSession = cameraCaptureSession; 722 | try { 723 | // Auto focus should be continuous for camera preview. 724 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, 725 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 726 | // Flash is automatically enabled when necessary. 727 | setAutoFlash(mPreviewRequestBuilder); 728 | 729 | // Finally, we start displaying the camera preview. 730 | mPreviewRequest = mPreviewRequestBuilder.build(); 731 | mCaptureSession.setRepeatingRequest(mPreviewRequest, 732 | mCaptureCallback, mBackgroundHandler); 733 | } catch (CameraAccessException e) { 734 | e.printStackTrace(); 735 | } 736 | } 737 | 738 | @Override 739 | public void onConfigureFailed( 740 | @NonNull CameraCaptureSession cameraCaptureSession) { 741 | showToast("Failed"); 742 | } 743 | }, null 744 | ); 745 | } catch (CameraAccessException e) { 746 | e.printStackTrace(); 747 | } 748 | } 749 | 750 | /** 751 | * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. 752 | * This method should be called after the camera preview size is determined in 753 | * setUpCameraOutputs and also the size of `mTextureView` is fixed. 754 | * 755 | * @param viewWidth The width of `mTextureView` 756 | * @param viewHeight The height of `mTextureView` 757 | */ 758 | private void configureTransform(int viewWidth, int viewHeight) { 759 | Activity activity = getActivity(); 760 | if (null == mTextureView || null == mPreviewSize || null == activity) { 761 | return; 762 | } 763 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 764 | Matrix matrix = new Matrix(); 765 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 766 | RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 767 | float centerX = viewRect.centerX(); 768 | float centerY = viewRect.centerY(); 769 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 770 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 771 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 772 | float scale = Math.max( 773 | (float) viewHeight / mPreviewSize.getHeight(), 774 | (float) viewWidth / mPreviewSize.getWidth()); 775 | matrix.postScale(scale, scale, centerX, centerY); 776 | matrix.postRotate(90 * (rotation - 2), centerX, centerY); 777 | } else if (Surface.ROTATION_180 == rotation) { 778 | matrix.postRotate(180, centerX, centerY); 779 | } 780 | mTextureView.setTransform(matrix); 781 | } 782 | 783 | /** 784 | * Initiate a still image capture. 785 | */ 786 | private void takePicture() { 787 | lockFocus(); 788 | } 789 | 790 | /** 791 | * Lock the focus as the first step for a still image capture. 792 | */ 793 | private void lockFocus() { 794 | try { 795 | // This is how to tell the camera to lock focus. 796 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 797 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 798 | // Tell #mCaptureCallback to wait for the lock. 799 | mState = STATE_WAITING_LOCK; 800 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 801 | mBackgroundHandler); 802 | } catch (CameraAccessException e) { 803 | e.printStackTrace(); 804 | } 805 | } 806 | 807 | /** 808 | * Run the precapture sequence for capturing a still image. This method should be called when 809 | * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}. 810 | */ 811 | private void runPrecaptureSequence() { 812 | try { 813 | // This is how to tell the camera to trigger. 814 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 815 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); 816 | // Tell #mCaptureCallback to wait for the precapture sequence to be set. 817 | mState = STATE_WAITING_PRECAPTURE; 818 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 819 | mBackgroundHandler); 820 | } catch (CameraAccessException e) { 821 | e.printStackTrace(); 822 | } 823 | } 824 | 825 | /** 826 | * Capture a still picture. This method should be called when we get a response in 827 | * {@link #mCaptureCallback} from both {@link #lockFocus()}. 828 | */ 829 | private void captureStillPicture() { 830 | try { 831 | final Activity activity = getActivity(); 832 | if (null == activity || null == mCameraDevice) { 833 | return; 834 | } 835 | // This is the CaptureRequest.Builder that we use to take a picture. 836 | final CaptureRequest.Builder captureBuilder = 837 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 838 | captureBuilder.addTarget(mImageReader.getSurface()); 839 | 840 | // Use the same AE and AF modes as the preview. 841 | captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, 842 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 843 | setAutoFlash(captureBuilder); 844 | 845 | // Orientation 846 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 847 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); 848 | 849 | CameraCaptureSession.CaptureCallback CaptureCallback 850 | = new CameraCaptureSession.CaptureCallback() { 851 | 852 | @Override 853 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 854 | @NonNull CaptureRequest request, 855 | @NonNull TotalCaptureResult result) { 856 | // showToast("Saved: " + mFile); 857 | counter++; 858 | getActivity().runOnUiThread(new Runnable() { 859 | @Override 860 | public void run() { 861 | updateCounter(); 862 | } 863 | }); 864 | Log.d(TAG, mFile.toString()); 865 | unlockFocus(); 866 | } 867 | }; 868 | 869 | mCaptureSession.stopRepeating(); 870 | mCaptureSession.abortCaptures(); 871 | mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null); 872 | } catch (CameraAccessException e) { 873 | e.printStackTrace(); 874 | } 875 | } 876 | 877 | @UiThread 878 | private void updateCounter() { 879 | tvCounter.setText("" + counter); 880 | } 881 | 882 | /** 883 | * Retrieves the JPEG orientation from the specified screen rotation. 884 | * 885 | * @param rotation The screen rotation. 886 | * @return The JPEG orientation (one of 0, 90, 270, and 360) 887 | */ 888 | private int getOrientation(int rotation) { 889 | // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X) 890 | // We have to take that into account and rotate JPEG properly. 891 | // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS. 892 | // For devices with orientation of 270, we need to rotate the JPEG 180 degrees. 893 | return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360; 894 | } 895 | 896 | /** 897 | * Unlock the focus. This method should be called when still image capture sequence is 898 | * finished. 899 | */ 900 | private void unlockFocus() { 901 | try { 902 | // Reset the auto-focus trigger 903 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 904 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 905 | setAutoFlash(mPreviewRequestBuilder); 906 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, 907 | mBackgroundHandler); 908 | // After this, the camera will go back to the normal state of preview. 909 | mState = STATE_PREVIEW; 910 | mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, 911 | mBackgroundHandler); 912 | } catch (CameraAccessException e) { 913 | e.printStackTrace(); 914 | } 915 | } 916 | 917 | @Override 918 | public void onClick(View view) { 919 | switch (view.getId()) { 920 | case R.id.picture: { 921 | takePicture(); 922 | break; 923 | } 924 | case R.id.info: { 925 | Activity activity = getActivity(); 926 | if (null != activity) { 927 | new AlertDialog.Builder(activity) 928 | .setMessage(R.string.intro_message) 929 | .setPositiveButton(android.R.string.ok, null) 930 | .show(); 931 | } 932 | break; 933 | } 934 | } 935 | } 936 | 937 | private void setAutoFlash(CaptureRequest.Builder requestBuilder) { 938 | if (mFlashSupported && false) { 939 | requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, 940 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); 941 | } 942 | } 943 | 944 | /** 945 | * Saves a JPEG {@link Image} into the specified {@link File}. 946 | */ 947 | private static class ImageSaver implements Runnable { 948 | 949 | /** 950 | * The JPEG image 951 | */ 952 | private final Image mImage; 953 | /** 954 | * The file we save the image into. 955 | */ 956 | private File mFile; 957 | 958 | private final Context mContext; 959 | 960 | ImageSaver(Image image, File file, Context context) { 961 | mImage = image; 962 | mFile = file; 963 | mContext = context; 964 | } 965 | 966 | @Override 967 | public void run() { 968 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 969 | byte[] bytes = new byte[buffer.remaining()]; 970 | buffer.get(bytes); 971 | FileOutputStream output = null; 972 | mFile = getFileName(); 973 | try { 974 | output = new FileOutputStream(mFile); 975 | output.write(bytes); 976 | } catch (IOException e) { 977 | e.printStackTrace(); 978 | } finally { 979 | mImage.close(); 980 | if (null != output) { 981 | try { 982 | output.close(); 983 | } catch (IOException e) { 984 | e.printStackTrace(); 985 | } 986 | } 987 | } 988 | } 989 | 990 | private File getFileName() { 991 | return new File(mContext.getExternalFilesDir(null), "pic" + System.currentTimeMillis() + ".jpg"); 992 | } 993 | 994 | } 995 | 996 | /** 997 | * Compares two {@code Size}s based on their areas. 998 | */ 999 | static class CompareSizesByArea implements Comparator { 1000 | 1001 | @Override 1002 | public int compare(Size lhs, Size rhs) { 1003 | // We cast here to ensure the multiplications won't overflow 1004 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 1005 | (long) rhs.getWidth() * rhs.getHeight()); 1006 | } 1007 | 1008 | } 1009 | 1010 | /** 1011 | * Shows an error message dialog. 1012 | */ 1013 | public static class ErrorDialog extends DialogFragment { 1014 | 1015 | private static final String ARG_MESSAGE = "message"; 1016 | 1017 | public static ErrorDialog newInstance(String message) { 1018 | ErrorDialog dialog = new ErrorDialog(); 1019 | Bundle args = new Bundle(); 1020 | args.putString(ARG_MESSAGE, message); 1021 | dialog.setArguments(args); 1022 | return dialog; 1023 | } 1024 | 1025 | @NonNull 1026 | @Override 1027 | public Dialog onCreateDialog(Bundle savedInstanceState) { 1028 | final Activity activity = getActivity(); 1029 | return new AlertDialog.Builder(activity) 1030 | .setMessage(getArguments().getString(ARG_MESSAGE)) 1031 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 1032 | @Override 1033 | public void onClick(DialogInterface dialogInterface, int i) { 1034 | activity.finish(); 1035 | } 1036 | }) 1037 | .create(); 1038 | } 1039 | 1040 | } 1041 | 1042 | } 1043 | -------------------------------------------------------------------------------- /app/src/main/java/com/twoeightnine/root/camera/CameraActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 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.twoeightnine.root.camera; 18 | 19 | import android.os.Bundle; 20 | import android.support.v7.app.AppCompatActivity; 21 | 22 | public class CameraActivity extends AppCompatActivity { 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_camera); 28 | if (null == savedInstanceState) { 29 | getSupportFragmentManager().beginTransaction() 30 | .replace(R.id.container, Camera2BasicFragment.newInstance()) 31 | .commit(); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-hdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/tile.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-hdpi/tile.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-mdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-xhdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_action_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-xxhdpi/ic_action_info.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwoEightNine/AndroidSpyCamera/abee050efb63d9bcd75a63973139af7802624642/app/src/main/res/drawable/task.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout-land/fragment_camera2_basic.xml: -------------------------------------------------------------------------------- 1 | 16 | 19 | 20 | 27 | 28 | 38 | 39 |