├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── darylgo │ │ └── camera2 │ │ └── sample │ │ ├── CameraCharacteristicsExts.kt │ │ ├── CameraPreview.kt │ │ ├── MainActivity.kt │ │ └── SettableFuture.kt │ └── res │ ├── drawable-hdpi │ ├── ic_capture_image.png │ ├── ic_image_thumbnail.png │ └── ic_switch_camera.png │ ├── drawable-mdpi │ ├── ic_capture_image.png │ ├── ic_image_thumbnail.png │ └── ic_switch_camera.png │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable-xhdpi │ ├── ic_capture_image.png │ ├── ic_image_thumbnail.png │ └── ic_switch_camera.png │ ├── drawable-xxhdpi │ ├── ic_capture_image.png │ ├── ic_image_thumbnail.png │ └── ic_switch_camera.png │ ├── drawable-xxxhdpi │ ├── ic_capture_image.png │ ├── ic_image_thumbnail.png │ └── ic_switch_camera.png │ ├── drawable │ ├── bg_capture_button.xml │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── menu │ └── camera_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.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 │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── 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/caches/build_file_checksums.ser 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | .idea -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 28 7 | defaultConfig { 8 | applicationId "com.darylgo.camera2.sample" 9 | minSdkVersion 26 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | implementation fileTree(dir: 'libs', include: ['*.jar']) 25 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 26 | implementation 'androidx.appcompat:appcompat:1.0.0-alpha1' 27 | implementation 'androidx.constraintlayout:constraintlayout:1.1.2' 28 | implementation 'androidx.exifinterface:exifinterface:1.0.0' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/darylgo/camera2/sample/CameraCharacteristicsExts.kt: -------------------------------------------------------------------------------- 1 | package com.darylgo.camera2.sample 2 | 3 | import android.hardware.camera2.CameraCharacteristics 4 | 5 | /** 6 | * 判断相机的 Hardware Level 是否大于等于指定的 Level。 7 | */ 8 | fun CameraCharacteristics.isHardwareLevelSupported(requiredLevel: Int): Boolean { 9 | val sortedLevels = intArrayOf( 10 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, 11 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, 12 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, 13 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 14 | ) 15 | val deviceLevel = this[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL] 16 | if (requiredLevel == deviceLevel) { 17 | return true 18 | } 19 | for (sortedLevel in sortedLevels) { 20 | if (requiredLevel == sortedLevel) { 21 | return true 22 | } else if (deviceLevel == sortedLevel) { 23 | return false 24 | } 25 | } 26 | return false 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/darylgo/camera2/sample/CameraPreview.kt: -------------------------------------------------------------------------------- 1 | package com.darylgo.camera2.sample 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.TextureView 6 | 7 | /** 8 | * 9 | * 10 | * @author hjd 2019.01.24 11 | */ 12 | class CameraPreview @JvmOverloads constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int = 0) : TextureView(context, attrs, defStyleAttr) { 13 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 14 | val width = MeasureSpec.getSize(widthMeasureSpec) 15 | setMeasuredDimension(width, width / 3 * 4) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/darylgo/camera2/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.darylgo.camera2.sample 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ContentValues 5 | import android.content.Context 6 | import android.content.pm.PackageManager 7 | import android.graphics.* 8 | import android.hardware.camera2.* 9 | import android.location.Location 10 | import android.location.LocationManager 11 | import android.media.ImageReader 12 | import android.media.MediaActionSound 13 | import android.os.* 14 | import android.provider.MediaStore 15 | import android.util.Log 16 | import android.util.Size 17 | import android.view.* 18 | import android.widget.ImageButton 19 | import android.widget.ImageView 20 | import androidx.annotation.AnyThread 21 | import androidx.annotation.MainThread 22 | import androidx.annotation.WorkerThread 23 | import androidx.appcompat.app.AppCompatActivity 24 | import androidx.core.content.ContextCompat 25 | import androidx.exifinterface.media.ExifInterface 26 | import java.io.File 27 | import java.text.DateFormat 28 | import java.text.SimpleDateFormat 29 | import java.util.* 30 | import java.util.concurrent.BlockingQueue 31 | import java.util.concurrent.Executor 32 | import java.util.concurrent.Executors 33 | import java.util.concurrent.LinkedBlockingDeque 34 | 35 | @SuppressLint("ClickableViewAccessibility") 36 | class MainActivity : AppCompatActivity(), Handler.Callback { 37 | 38 | companion object { 39 | private const val TAG: String = "MainActivity" 40 | private const val REQUEST_PERMISSION_CODE: Int = 1 41 | private val REQUIRED_PERMISSIONS: Array = arrayOf( 42 | android.Manifest.permission.CAMERA, 43 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, 44 | android.Manifest.permission.ACCESS_FINE_LOCATION 45 | ) 46 | private const val MSG_OPEN_CAMERA: Int = 1 47 | private const val MSG_CLOSE_CAMERA: Int = 2 48 | private const val MSG_CREATE_SESSION: Int = 3 49 | private const val MSG_CLOSE_SESSION: Int = 4 50 | private const val MSG_SET_PREVIEW_SIZE: Int = 5 51 | private const val MSG_START_PREVIEW: Int = 6 52 | private const val MSG_STOP_PREVIEW: Int = 7 53 | private const val MSG_SET_IMAGE_SIZE: Int = 8 54 | private const val MSG_CAPTURE_IMAGE: Int = 9 55 | private const val MSG_CAPTURE_IMAGE_BURST: Int = 10 56 | private const val MSG_START_CAPTURE_IMAGE_CONTINUOUSLY: Int = 11 57 | private const val MSG_CREATE_REQUEST_BUILDERS: Int = 12 58 | 59 | private const val MAX_PREVIEW_WIDTH: Int = 1440 60 | private const val MAX_PREVIEW_HEIGHT: Int = 1080 61 | private const val MAX_IMAGE_WIDTH: Int = 4032 62 | private const val MAX_IMAGE_HEIGHT: Int = 3024 63 | } 64 | 65 | private val mainHandler: Handler = Handler(Looper.getMainLooper()) 66 | private val mediaActionSound: MediaActionSound = MediaActionSound() 67 | private val saveImageExecutor: Executor = Executors.newSingleThreadExecutor() 68 | private val deviceOrientationListener: DeviceOrientationListener by lazy { DeviceOrientationListener(this) } 69 | private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) } 70 | private val cameraPreview: CameraPreview by lazy { findViewById(R.id.camera_preview) } 71 | private val thumbnailView: ImageView by lazy { findViewById(R.id.thumbnail_view) } 72 | private val captureImageButton: ImageButton by lazy { findViewById(R.id.capture_image_button) } 73 | private val captureResults: BlockingQueue = LinkedBlockingDeque() 74 | private var cameraThread: HandlerThread? = null 75 | private var cameraHandler: Handler? = null 76 | private var frontCameraId: String? = null 77 | private var frontCameraCharacteristics: CameraCharacteristics? = null 78 | private var backCameraId: String? = null 79 | private var backCameraCharacteristics: CameraCharacteristics? = null 80 | private var cameraDeviceFuture: SettableFuture? = null 81 | private var cameraCharacteristicsFuture: SettableFuture? = null 82 | private var captureSessionFuture: SettableFuture? = null 83 | private var previewSurfaceTextureFuture: SettableFuture? = null 84 | private var previewSurface: Surface? = null 85 | private var previewDataImageReader: ImageReader? = null 86 | private var previewDataSurface: Surface? = null 87 | private var jpegImageReader: ImageReader? = null 88 | private var jpegSurface: Surface? = null 89 | private var previewImageRequestBuilder: CaptureRequest.Builder? = null 90 | private var captureImageRequestBuilder: CaptureRequest.Builder? = null 91 | 92 | @SuppressLint("MissingPermission") 93 | override fun handleMessage(msg: Message): Boolean { 94 | when (msg.what) { 95 | MSG_OPEN_CAMERA -> { 96 | Log.d(TAG, "Handle message: MSG_OPEN_CAMERA") 97 | cameraDeviceFuture = SettableFuture() 98 | cameraCharacteristicsFuture = SettableFuture() 99 | val cameraId = msg.obj as String 100 | val cameraStateCallback = CameraStateCallback() 101 | cameraManager.openCamera(cameraId, cameraStateCallback, mainHandler) 102 | } 103 | MSG_CLOSE_CAMERA -> { 104 | Log.d(TAG, "Handle message: MSG_CLOSE_CAMERA") 105 | val cameraDevice = cameraDeviceFuture?.get() 106 | cameraDevice?.close() 107 | cameraDeviceFuture = null 108 | cameraCharacteristicsFuture = null 109 | } 110 | MSG_CREATE_SESSION -> { 111 | Log.d(TAG, "Handle message: MSG_CREATE_SESSION") 112 | val sessionStateCallback = SessionStateCallback() 113 | val outputs = mutableListOf() 114 | val previewSurface = previewSurface 115 | val previewDataSurface = previewDataSurface 116 | val jpegSurface = jpegSurface 117 | outputs.add(previewSurface!!) 118 | if (previewDataSurface != null) { 119 | outputs.add(previewDataSurface) 120 | } 121 | if (jpegSurface != null) { 122 | outputs.add(jpegSurface) 123 | } 124 | captureSessionFuture = SettableFuture() 125 | val cameraDevice = cameraDeviceFuture?.get() 126 | cameraDevice?.createCaptureSession(outputs, sessionStateCallback, mainHandler) 127 | } 128 | MSG_CLOSE_SESSION -> { 129 | Log.d(TAG, "Handle message: MSG_CLOSE_SESSION") 130 | val captureSession = captureSessionFuture?.get() 131 | captureSession?.close() 132 | captureSessionFuture = null 133 | } 134 | MSG_SET_PREVIEW_SIZE -> { 135 | Log.d(TAG, "Handle message: MSG_SET_PREVIEW_SIZE") 136 | val cameraCharacteristics = cameraCharacteristicsFuture?.get() 137 | if (cameraCharacteristics != null) { 138 | // Get the optimal preview size according to the specified max width and max height. 139 | val maxWidth = msg.arg1 140 | val maxHeight = msg.arg2 141 | val previewSize = getOptimalSize(cameraCharacteristics, SurfaceTexture::class.java, maxWidth, maxHeight)!! 142 | // Set the SurfaceTexture's buffer size with preview size. 143 | val previewSurfaceTexture = previewSurfaceTextureFuture!!.get()!! 144 | previewSurfaceTexture.setDefaultBufferSize(previewSize.width, previewSize.height) 145 | previewSurface = Surface(previewSurfaceTexture) 146 | // Set up an ImageReader to receive preview frame data if YUV_420_888 is supported. 147 | val imageFormat = ImageFormat.YUV_420_888 148 | val streamConfigurationMap = cameraCharacteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP] 149 | if (streamConfigurationMap?.isOutputSupportedFor(imageFormat) == true) { 150 | previewDataImageReader = ImageReader.newInstance(previewSize.width, previewSize.height, imageFormat, 3) 151 | previewDataImageReader?.setOnImageAvailableListener(OnPreviewDataAvailableListener(), cameraHandler) 152 | previewDataSurface = previewDataImageReader?.surface 153 | } 154 | } 155 | } 156 | MSG_CREATE_REQUEST_BUILDERS -> { 157 | Log.d(TAG, "Handle message: MSG_CREATE_REQUEST_BUILDERS") 158 | val cameraDevice = cameraDeviceFuture?.get() 159 | if (cameraDevice != null) { 160 | previewImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) 161 | captureImageRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) 162 | } 163 | } 164 | MSG_START_PREVIEW -> { 165 | Log.d(TAG, "Handle message: MSG_START_PREVIEW") 166 | val cameraDevice = cameraDeviceFuture?.get() 167 | val captureSession = captureSessionFuture?.get() 168 | val previewImageRequestBuilder = previewImageRequestBuilder!! 169 | val captureImageRequestBuilder = captureImageRequestBuilder!! 170 | if (cameraDevice != null && captureSession != null) { 171 | val previewSurface = previewSurface!! 172 | val previewDataSurface = previewDataSurface 173 | previewImageRequestBuilder.addTarget(previewSurface) 174 | // Avoid missing preview frame while capturing image. 175 | captureImageRequestBuilder.addTarget(previewSurface) 176 | if (previewDataSurface != null) { 177 | previewImageRequestBuilder.addTarget(previewDataSurface) 178 | // Avoid missing preview data while capturing image. 179 | captureImageRequestBuilder.addTarget(previewDataSurface) 180 | } 181 | val previewRequest = previewImageRequestBuilder.build() 182 | captureSession.setRepeatingRequest(previewRequest, RepeatingCaptureStateCallback(), mainHandler) 183 | } 184 | } 185 | MSG_STOP_PREVIEW -> { 186 | Log.d(TAG, "Handle message: MSG_STOP_PREVIEW") 187 | val captureSession = captureSessionFuture?.get() 188 | captureSession?.stopRepeating() 189 | } 190 | MSG_SET_IMAGE_SIZE -> { 191 | Log.d(TAG, "Handle message: MSG_SET_IMAGE_SIZE") 192 | val cameraCharacteristics = cameraCharacteristicsFuture?.get() 193 | val captureImageRequestBuilder = captureImageRequestBuilder 194 | if (cameraCharacteristics != null && captureImageRequestBuilder != null) { 195 | // Create a JPEG ImageReader instance according to the image size. 196 | val maxWidth = msg.arg1 197 | val maxHeight = msg.arg2 198 | val imageSize = getOptimalSize(cameraCharacteristics, ImageReader::class.java, maxWidth, maxHeight)!! 199 | jpegImageReader = ImageReader.newInstance(imageSize.width, imageSize.height, ImageFormat.JPEG, 5) 200 | jpegImageReader?.setOnImageAvailableListener(OnJpegImageAvailableListener(), cameraHandler) 201 | jpegSurface = jpegImageReader?.surface 202 | // Configure the thumbnail size if any suitable size found, no thumbnail will be generated if the thumbnail size is null. 203 | val availableThumbnailSizes = cameraCharacteristics[CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES] 204 | val thumbnailSize = getOptimalSize(availableThumbnailSizes, maxWidth, maxHeight) 205 | captureImageRequestBuilder[CaptureRequest.JPEG_THUMBNAIL_SIZE] = thumbnailSize 206 | } 207 | } 208 | MSG_CAPTURE_IMAGE -> { 209 | Log.d(TAG, "Handle message: MSG_CAPTURE_IMAGE") 210 | val cameraCharacteristics = cameraCharacteristicsFuture?.get() 211 | val captureSession = captureSessionFuture?.get() 212 | val captureImageRequestBuilder = captureImageRequestBuilder 213 | val jpegSurface = jpegSurface 214 | if (captureSession != null && captureImageRequestBuilder != null && jpegSurface != null && cameraCharacteristics != null) { 215 | // Configure the jpeg orientation according to the device orientation. 216 | val deviceOrientation = deviceOrientationListener.orientation 217 | val jpegOrientation = getJpegOrientation(cameraCharacteristics, deviceOrientation) 218 | captureImageRequestBuilder[CaptureRequest.JPEG_ORIENTATION] = jpegOrientation 219 | 220 | // Configure the location information. 221 | val location = getLocation() 222 | captureImageRequestBuilder[CaptureRequest.JPEG_GPS_LOCATION] = location 223 | 224 | // Configure the image quality. 225 | captureImageRequestBuilder[CaptureRequest.JPEG_QUALITY] = 100 226 | 227 | // Add the target surface to receive the jpeg image data. 228 | captureImageRequestBuilder.addTarget(jpegSurface) 229 | 230 | val captureImageRequest = captureImageRequestBuilder.build() 231 | captureSession.capture(captureImageRequest, CaptureImageStateCallback(), mainHandler) 232 | } 233 | } 234 | MSG_CAPTURE_IMAGE_BURST -> { 235 | Log.d(TAG, "Handle message: MSG_CAPTURE_IMAGE_BURST") 236 | val cameraCharacteristics = cameraCharacteristicsFuture?.get() 237 | val burstNumber = msg.arg1 238 | val captureSession = captureSessionFuture?.get() 239 | val captureImageRequestBuilder = captureImageRequestBuilder 240 | val jpegSurface = jpegSurface 241 | if (captureSession != null && captureImageRequestBuilder != null && jpegSurface != null && cameraCharacteristics != null) { 242 | // Configure the jpeg orientation according to the device orientation. 243 | val deviceOrientation = deviceOrientationListener.orientation 244 | val jpegOrientation = getJpegOrientation(cameraCharacteristics, deviceOrientation) 245 | captureImageRequestBuilder[CaptureRequest.JPEG_ORIENTATION] = jpegOrientation 246 | 247 | // Configure the location information. 248 | val location = getLocation() 249 | captureImageRequestBuilder[CaptureRequest.JPEG_GPS_LOCATION] = location 250 | 251 | // Configure the image quality. 252 | captureImageRequestBuilder[CaptureRequest.JPEG_QUALITY] = 100 253 | 254 | // Add the target surface to receive the jpeg image data. 255 | captureImageRequestBuilder.addTarget(jpegSurface) 256 | 257 | // Use the burst mode to capture images sequentially. 258 | val captureImageRequest = captureImageRequestBuilder.build() 259 | val captureImageRequests = mutableListOf() 260 | for (i in 1..burstNumber) { 261 | captureImageRequests.add(captureImageRequest) 262 | } 263 | captureSession.captureBurst(captureImageRequests, CaptureImageStateCallback(), mainHandler) 264 | } 265 | } 266 | MSG_START_CAPTURE_IMAGE_CONTINUOUSLY -> { 267 | Log.d(TAG, "Handle message: MSG_START_CAPTURE_IMAGE_CONTINUOUSLY") 268 | val cameraCharacteristics = cameraCharacteristicsFuture?.get() 269 | val captureSession = captureSessionFuture?.get() 270 | val captureImageRequestBuilder = captureImageRequestBuilder 271 | val jpegSurface = jpegSurface 272 | if (captureSession != null && captureImageRequestBuilder != null && jpegSurface != null && cameraCharacteristics != null) { 273 | // Configure the jpeg orientation according to the device orientation. 274 | val deviceOrientation = deviceOrientationListener.orientation 275 | val jpegOrientation = getJpegOrientation(cameraCharacteristics, deviceOrientation) 276 | captureImageRequestBuilder[CaptureRequest.JPEG_ORIENTATION] = jpegOrientation 277 | 278 | // Configure the location information. 279 | val location = getLocation() 280 | captureImageRequestBuilder[CaptureRequest.JPEG_GPS_LOCATION] = location 281 | 282 | // Configure the image quality. 283 | captureImageRequestBuilder[CaptureRequest.JPEG_QUALITY] = 100 284 | 285 | // Add the target surface to receive the jpeg image data. 286 | captureImageRequestBuilder.addTarget(jpegSurface) 287 | 288 | // Use the repeating mode to capture image continuously. 289 | val captureImageRequest = captureImageRequestBuilder.build() 290 | captureSession.setRepeatingRequest(captureImageRequest, CaptureImageStateCallback(), mainHandler) 291 | } 292 | } 293 | } 294 | return false 295 | } 296 | 297 | override fun onCreate(savedInstanceState: Bundle?) { 298 | super.onCreate(savedInstanceState) 299 | setContentView(R.layout.activity_main) 300 | startCameraThread() 301 | 302 | val cameraIdList = cameraManager.cameraIdList 303 | cameraIdList.forEach { cameraId -> 304 | val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) 305 | if (cameraCharacteristics.isHardwareLevelSupported(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL)) { 306 | if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) { 307 | frontCameraId = cameraId 308 | frontCameraCharacteristics = cameraCharacteristics 309 | } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) { 310 | backCameraId = cameraId 311 | backCameraCharacteristics = cameraCharacteristics 312 | } 313 | } 314 | } 315 | 316 | previewSurfaceTextureFuture = SettableFuture() 317 | cameraPreview.surfaceTextureListener = PreviewSurfaceTextureListener() 318 | captureImageButton.setOnTouchListener(CaptureImageButtonListener(this)) 319 | } 320 | 321 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 322 | menuInflater.inflate(R.menu.camera_menu, menu) 323 | return true 324 | } 325 | 326 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 327 | return when (item.itemId) { 328 | R.id.switch_camera -> { 329 | switchCamera() 330 | true 331 | } 332 | else -> super.onOptionsItemSelected(item) 333 | } 334 | } 335 | 336 | override fun onResume() { 337 | super.onResume() 338 | deviceOrientationListener.enable() 339 | if (checkRequiredPermissions()) { 340 | val cameraId = backCameraId ?: frontCameraId!! 341 | openCamera(cameraId) 342 | createCaptureRequestBuilders() 343 | setPreviewSize(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT) 344 | setImageSize(MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT) 345 | createSession() 346 | startPreview() 347 | } 348 | } 349 | 350 | override fun onPause() { 351 | super.onPause() 352 | deviceOrientationListener.disable() 353 | closeCamera() 354 | previewDataImageReader?.close() 355 | jpegImageReader?.close() 356 | } 357 | 358 | override fun onDestroy() { 359 | super.onDestroy() 360 | stopCameraThread() 361 | mediaActionSound.release() 362 | } 363 | 364 | private fun startCameraThread() { 365 | cameraThread = HandlerThread("CameraThread") 366 | cameraThread!!.start() 367 | cameraHandler = Handler(cameraThread!!.looper, this) 368 | } 369 | 370 | private fun stopCameraThread() { 371 | cameraThread?.quitSafely() 372 | cameraThread = null 373 | cameraHandler = null 374 | } 375 | 376 | @MainThread 377 | private fun openCamera(cameraId: String) { 378 | cameraHandler?.obtainMessage(MSG_OPEN_CAMERA, cameraId)?.sendToTarget() 379 | } 380 | 381 | @MainThread 382 | private fun switchCamera() { 383 | val cameraDevice = cameraDeviceFuture?.get() 384 | val oldCameraId = cameraDevice?.id 385 | val newCameraId = if (oldCameraId == frontCameraId) backCameraId else frontCameraId 386 | if (newCameraId != null) { 387 | closeCamera() 388 | openCamera(newCameraId) 389 | createCaptureRequestBuilders() 390 | setPreviewSize(MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT) 391 | setImageSize(MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT) 392 | createSession() 393 | startPreview() 394 | } 395 | } 396 | 397 | @MainThread 398 | private fun closeCamera() { 399 | cameraHandler?.sendEmptyMessage(MSG_CLOSE_CAMERA) 400 | } 401 | 402 | @MainThread 403 | private fun setPreviewSize(maxWidth: Int, maxHeight: Int) { 404 | cameraHandler?.obtainMessage(MSG_SET_PREVIEW_SIZE, maxWidth, maxHeight)?.sendToTarget() 405 | } 406 | 407 | @MainThread 408 | private fun setImageSize(maxWidth: Int, maxHeight: Int) { 409 | cameraHandler?.obtainMessage(MSG_SET_IMAGE_SIZE, maxWidth, maxHeight)?.sendToTarget() 410 | } 411 | 412 | @MainThread 413 | private fun createSession() { 414 | cameraHandler?.sendEmptyMessage(MSG_CREATE_SESSION) 415 | } 416 | 417 | @MainThread 418 | private fun closeSession() { 419 | cameraHandler?.sendEmptyMessage(MSG_CLOSE_SESSION) 420 | } 421 | 422 | @MainThread 423 | private fun createCaptureRequestBuilders() { 424 | cameraHandler?.sendEmptyMessage(MSG_CREATE_REQUEST_BUILDERS) 425 | } 426 | 427 | @MainThread 428 | private fun startPreview() { 429 | cameraHandler?.sendEmptyMessage(MSG_START_PREVIEW) 430 | } 431 | 432 | @MainThread 433 | private fun stopPreview() { 434 | cameraHandler?.sendEmptyMessage(MSG_STOP_PREVIEW) 435 | } 436 | 437 | @MainThread 438 | private fun captureImage() { 439 | cameraHandler?.sendEmptyMessage(MSG_CAPTURE_IMAGE) 440 | } 441 | 442 | @MainThread 443 | private fun captureImageBurst(burstNumber: Int) { 444 | cameraHandler?.obtainMessage(MSG_CAPTURE_IMAGE_BURST, burstNumber, 0)?.sendToTarget() 445 | } 446 | 447 | @MainThread 448 | private fun startCaptureImageContinuously() { 449 | cameraHandler?.sendEmptyMessage(MSG_START_CAPTURE_IMAGE_CONTINUOUSLY) 450 | } 451 | 452 | @MainThread 453 | private fun stopCaptureImageContinuously() { 454 | // Restart preview to stop the continuous image capture. 455 | startPreview() 456 | } 457 | 458 | @WorkerThread 459 | private fun getOptimalSize(cameraCharacteristics: CameraCharacteristics, clazz: Class<*>, maxWidth: Int, maxHeight: Int): Size? { 460 | val streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) 461 | val supportedSizes = streamConfigurationMap?.getOutputSizes(clazz) 462 | return getOptimalSize(supportedSizes, maxWidth, maxHeight) 463 | } 464 | 465 | @AnyThread 466 | private fun getOptimalSize(supportedSizes: Array?, maxWidth: Int, maxHeight: Int): Size? { 467 | val aspectRatio = maxWidth.toFloat() / maxHeight 468 | if (supportedSizes != null) { 469 | for (size in supportedSizes) { 470 | if (size.width.toFloat() / size.height == aspectRatio && size.height <= maxHeight && size.width <= maxWidth) { 471 | return size 472 | } 473 | } 474 | } 475 | return null 476 | } 477 | 478 | private fun getDisplayRotation(cameraCharacteristics: CameraCharacteristics): Int { 479 | val rotation = windowManager.defaultDisplay.rotation 480 | val degrees = when (rotation) { 481 | Surface.ROTATION_0 -> 0 482 | Surface.ROTATION_90 -> 90 483 | Surface.ROTATION_180 -> 180 484 | Surface.ROTATION_270 -> 270 485 | else -> 0 486 | } 487 | val sensorOrientation = cameraCharacteristics[CameraCharacteristics.SENSOR_ORIENTATION]!! 488 | return if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) { 489 | (360 - (sensorOrientation + degrees) % 360) % 360 490 | } else { 491 | (sensorOrientation - degrees + 360) % 360 492 | } 493 | } 494 | 495 | private fun getJpegOrientation(cameraCharacteristics: CameraCharacteristics, deviceOrientation: Int): Int { 496 | var myDeviceOrientation = deviceOrientation 497 | if (myDeviceOrientation == android.view.OrientationEventListener.ORIENTATION_UNKNOWN) { 498 | return 0 499 | } 500 | val sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!! 501 | 502 | // Round device orientation to a multiple of 90 503 | myDeviceOrientation = (myDeviceOrientation + 45) / 90 * 90 504 | 505 | // Reverse device orientation for front-facing cameras 506 | val facingFront = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT 507 | if (facingFront) { 508 | myDeviceOrientation = -myDeviceOrientation 509 | } 510 | 511 | // Calculate desired JPEG orientation relative to camera orientation to make 512 | // the image upright relative to the device orientation 513 | return (sensorOrientation + myDeviceOrientation + 360) % 360 514 | } 515 | 516 | /** 517 | * Returns a thumbnail bitmap from the specified jpeg file. 518 | */ 519 | @WorkerThread 520 | private fun getThumbnail(jpegPath: String): Bitmap? { 521 | val exifInterface = ExifInterface(jpegPath) 522 | val orientationFlag = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) 523 | val orientation = when (orientationFlag) { 524 | ExifInterface.ORIENTATION_NORMAL -> 0.0F 525 | ExifInterface.ORIENTATION_ROTATE_90 -> 90.0F 526 | ExifInterface.ORIENTATION_ROTATE_180 -> 180.0F 527 | ExifInterface.ORIENTATION_ROTATE_270 -> 270.0F 528 | else -> 0.0F 529 | } 530 | 531 | var thumbnail = if (exifInterface.hasThumbnail()) { 532 | exifInterface.thumbnailBitmap 533 | } else { 534 | val options = BitmapFactory.Options() 535 | options.inSampleSize = 16 536 | BitmapFactory.decodeFile(jpegPath, options) 537 | } 538 | 539 | if (orientation != 0.0F && thumbnail != null) { 540 | val matrix = Matrix() 541 | matrix.setRotate(orientation) 542 | thumbnail = Bitmap.createBitmap(thumbnail, 0, 0, thumbnail.width, thumbnail.height, matrix, true) 543 | } 544 | 545 | return thumbnail 546 | } 547 | 548 | @WorkerThread 549 | private fun getLocation(): Location? { 550 | val locationManager = getSystemService(LocationManager::class.java) 551 | if (locationManager != null && ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { 552 | return locationManager.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER) 553 | } 554 | return null 555 | } 556 | 557 | /** 558 | * Checks if all required permissions are granted and requests denied permissions. 559 | * 560 | * @return Returns true if all required permissions are granted. 561 | */ 562 | private fun checkRequiredPermissions(): Boolean { 563 | val deniedPermissions = mutableListOf() 564 | for (permission in REQUIRED_PERMISSIONS) { 565 | if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) { 566 | deniedPermissions.add(permission) 567 | } 568 | } 569 | if (deniedPermissions.isEmpty().not()) { 570 | requestPermissions(deniedPermissions.toTypedArray(), REQUEST_PERMISSION_CODE) 571 | } 572 | return deniedPermissions.isEmpty() 573 | } 574 | 575 | private fun getCameraCharacteristics(cameraId: String): CameraCharacteristics? { 576 | return when (cameraId) { 577 | frontCameraId -> frontCameraCharacteristics 578 | backCameraId -> backCameraCharacteristics 579 | else -> null 580 | } 581 | } 582 | 583 | private inner class CameraStateCallback : CameraDevice.StateCallback() { 584 | @MainThread 585 | override fun onOpened(camera: CameraDevice) { 586 | cameraDeviceFuture!!.set(camera) 587 | cameraCharacteristicsFuture!!.set(getCameraCharacteristics(camera.id)) 588 | } 589 | 590 | @MainThread 591 | override fun onClosed(camera: CameraDevice) { 592 | 593 | } 594 | 595 | @MainThread 596 | override fun onDisconnected(camera: CameraDevice) { 597 | cameraDeviceFuture!!.set(camera) 598 | closeCamera() 599 | } 600 | 601 | @MainThread 602 | override fun onError(camera: CameraDevice, error: Int) { 603 | cameraDeviceFuture!!.set(camera) 604 | closeCamera() 605 | } 606 | } 607 | 608 | private inner class SessionStateCallback : CameraCaptureSession.StateCallback() { 609 | @MainThread 610 | override fun onConfigureFailed(session: CameraCaptureSession) { 611 | captureSessionFuture!!.set(session) 612 | } 613 | 614 | @MainThread 615 | override fun onConfigured(session: CameraCaptureSession) { 616 | captureSessionFuture!!.set(session) 617 | } 618 | 619 | @MainThread 620 | override fun onClosed(session: CameraCaptureSession) { 621 | 622 | } 623 | } 624 | 625 | private inner class PreviewSurfaceTextureListener : TextureView.SurfaceTextureListener { 626 | @MainThread 627 | override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) = Unit 628 | 629 | @MainThread 630 | override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) = Unit 631 | 632 | @MainThread 633 | override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean = false 634 | 635 | @MainThread 636 | override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) { 637 | previewSurfaceTextureFuture!!.set(surfaceTexture) 638 | } 639 | } 640 | 641 | private inner class RepeatingCaptureStateCallback : CameraCaptureSession.CaptureCallback() { 642 | @MainThread 643 | override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) { 644 | super.onCaptureStarted(session, request, timestamp, frameNumber) 645 | } 646 | 647 | @MainThread 648 | override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { 649 | super.onCaptureCompleted(session, request, result) 650 | } 651 | } 652 | 653 | private inner class CaptureImageButtonListener(context: Context) : GestureDetector.SimpleOnGestureListener(), View.OnTouchListener { 654 | 655 | private val gestureDetector: GestureDetector = GestureDetector(context, this) 656 | private var isLongPressed: Boolean = false 657 | 658 | override fun onTouch(view: View, event: MotionEvent): Boolean { 659 | if (event.actionMasked == MotionEvent.ACTION_UP && isLongPressed) { 660 | onLongPressUp() 661 | } 662 | return gestureDetector.onTouchEvent(event) 663 | } 664 | 665 | override fun onSingleTapConfirmed(event: MotionEvent): Boolean { 666 | captureImage() 667 | return true 668 | } 669 | 670 | override fun onLongPress(event: MotionEvent) { 671 | isLongPressed = true 672 | startCaptureImageContinuously() 673 | } 674 | 675 | private fun onLongPressUp() { 676 | isLongPressed = false 677 | stopCaptureImageContinuously() 678 | } 679 | 680 | override fun onDoubleTap(event: MotionEvent): Boolean { 681 | captureImageBurst(10) 682 | return true 683 | } 684 | 685 | } 686 | 687 | private inner class OnPreviewDataAvailableListener : ImageReader.OnImageAvailableListener { 688 | @WorkerThread 689 | override fun onImageAvailable(imageReader: ImageReader) { 690 | val image = imageReader.acquireNextImage() 691 | image?.use { 692 | val planes = image.planes 693 | val yPlane = planes[0] 694 | val uPlane = planes[1] 695 | val vPlane = planes[2] 696 | val yBuffer = yPlane.buffer // Frame data of Y channel 697 | val uBuffer = uPlane.buffer // Frame data of U channel 698 | val vBuffer = vPlane.buffer // Frame data of V channel 699 | } 700 | } 701 | } 702 | 703 | private inner class CaptureImageStateCallback : CameraCaptureSession.CaptureCallback() { 704 | 705 | @MainThread 706 | override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) { 707 | super.onCaptureStarted(session, request, timestamp, frameNumber) 708 | // Play the shutter click sound. 709 | cameraHandler?.post { mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) } 710 | } 711 | 712 | @MainThread 713 | override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { 714 | super.onCaptureCompleted(session, request, result) 715 | captureResults.put(result) 716 | } 717 | } 718 | 719 | private inner class DeviceOrientationListener(context: Context) : OrientationEventListener(context) { 720 | 721 | var orientation: Int = 0 722 | private set 723 | 724 | @MainThread 725 | override fun onOrientationChanged(orientation: Int) { 726 | this.orientation = orientation 727 | } 728 | } 729 | 730 | private inner class OnJpegImageAvailableListener : ImageReader.OnImageAvailableListener { 731 | 732 | private val dateFormat: DateFormat = SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault()) 733 | private val cameraDir: String = "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)}/Camera" 734 | 735 | @WorkerThread 736 | override fun onImageAvailable(imageReader: ImageReader) { 737 | val image = imageReader.acquireNextImage() 738 | val captureResult = captureResults.take() 739 | if (image != null && captureResult != null) { 740 | image.use { 741 | val jpegByteBuffer = it.planes[0].buffer// Jpeg image data only occupy the planes[0]. 742 | val jpegByteArray = ByteArray(jpegByteBuffer.remaining()) 743 | jpegByteBuffer.get(jpegByteArray) 744 | val width = it.width 745 | val height = it.height 746 | saveImageExecutor.execute { 747 | val date = System.currentTimeMillis() 748 | val title = "IMG_${dateFormat.format(date)}"// e.g. IMG_20190211100833786 749 | val displayName = "$title.jpeg"// e.g. IMG_20190211100833786.jpeg 750 | val path = "$cameraDir/$displayName"// e.g. /sdcard/DCIM/Camera/IMG_20190211100833786.jpeg 751 | val orientation = captureResult[CaptureResult.JPEG_ORIENTATION] 752 | val location = captureResult[CaptureResult.JPEG_GPS_LOCATION] 753 | val longitude = location?.longitude ?: 0.0 754 | val latitude = location?.latitude ?: 0.0 755 | 756 | // Write the jpeg data into the specified file. 757 | File(path).writeBytes(jpegByteArray) 758 | 759 | // Insert the image information into the media store. 760 | val values = ContentValues() 761 | values.put(MediaStore.Images.ImageColumns.TITLE, title) 762 | values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, displayName) 763 | values.put(MediaStore.Images.ImageColumns.DATA, path) 764 | values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, date) 765 | values.put(MediaStore.Images.ImageColumns.WIDTH, width) 766 | values.put(MediaStore.Images.ImageColumns.HEIGHT, height) 767 | values.put(MediaStore.Images.ImageColumns.ORIENTATION, orientation) 768 | values.put(MediaStore.Images.ImageColumns.LONGITUDE, longitude) 769 | values.put(MediaStore.Images.ImageColumns.LATITUDE, latitude) 770 | contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) 771 | 772 | // Refresh the thumbnail of image. 773 | val thumbnail = getThumbnail(path) 774 | if (thumbnail != null) { 775 | runOnUiThread { 776 | thumbnailView.setImageBitmap(thumbnail) 777 | thumbnailView.scaleX = 0.8F 778 | thumbnailView.scaleY = 0.8F 779 | thumbnailView.animate().setDuration(50).scaleX(1.0F).scaleY(1.0F).start() 780 | } 781 | } 782 | } 783 | } 784 | } 785 | } 786 | } 787 | 788 | } 789 | -------------------------------------------------------------------------------- /app/src/main/java/com/darylgo/camera2/sample/SettableFuture.kt: -------------------------------------------------------------------------------- 1 | package com.darylgo.camera2.sample 2 | 3 | import java.util.concurrent.* 4 | import java.util.concurrent.locks.AbstractQueuedSynchronizer 5 | 6 | /** 7 | * 支持通过 [set] 方法设置结果,并且解除线程等待的 [Future]。 8 | * 9 | * @author hjd 2018.07.26 10 | */ 11 | open class SettableFuture : Future { 12 | 13 | private val sync: Sync = Sync() 14 | 15 | /** 16 | * 设置结果,解除线程同步等待。 17 | */ 18 | fun set(value: V?): Boolean { 19 | return sync.set(value) 20 | } 21 | 22 | override fun isDone(): Boolean { 23 | return sync.isDone() 24 | } 25 | 26 | /** 27 | * 获取异步结果,如果结果还没有计算出来,则进入同步等待状态。 28 | */ 29 | override fun get(): V? { 30 | return sync.get() 31 | } 32 | 33 | /** 34 | * 获取异步结果,如果结果还没有计算出来,则进入同步等待状态,一段时间之后超时。 35 | */ 36 | override fun get(timeout: Long, unit: TimeUnit): V? { 37 | return sync[unit.toNanos(timeout)] 38 | } 39 | 40 | override fun cancel(mayInterruptIfRunning: Boolean): Boolean { 41 | return sync.cancel(mayInterruptIfRunning) 42 | } 43 | 44 | override fun isCancelled(): Boolean { 45 | return sync.isCancelled() 46 | } 47 | 48 | private class Sync : AbstractQueuedSynchronizer() { 49 | 50 | private var value: V? = null 51 | private var exception: Throwable? = null 52 | 53 | /** 54 | * Checks if the state is [.COMPLETED], [.CANCELLED], or [ ]. 55 | */ 56 | fun isDone(): Boolean { 57 | return state and (COMPLETED or CANCELLED or INTERRUPTED) != 0 58 | } 59 | 60 | /** 61 | * Checks if the state is [.CANCELLED] or [.INTERRUPTED]. 62 | */ 63 | fun isCancelled(): Boolean { 64 | return state and (CANCELLED or INTERRUPTED) != 0 65 | } 66 | 67 | /** 68 | * Acquisition succeeds if the future is done, otherwise it fails. 69 | */ 70 | override fun tryAcquireShared(ignored: Int): Int { 71 | return if (isDone()) { 72 | 1 73 | } else -1 74 | } 75 | 76 | /** 77 | * We always allow a release to go through, this means the state has been 78 | * successfully changed and the result is available. 79 | */ 80 | override fun tryReleaseShared(finalState: Int): Boolean { 81 | state = finalState 82 | return true 83 | } 84 | 85 | /** 86 | * Blocks until the task is complete or the timeout expires. Throws a 87 | * [TimeoutException] if the timer expires, otherwise behaves like 88 | * [.get]. 89 | */ 90 | operator fun get(nanos: Long): V? { 91 | // Attempt to acquire the shared lock with a timeout. 92 | if (!tryAcquireSharedNanos(-1, nanos)) { 93 | throw TimeoutException("Timeout waiting for task.") 94 | } 95 | return getValue() 96 | } 97 | 98 | /** 99 | * Blocks until [.complete] has been successfully called. Throws a [CancellationException] 100 | * if the task was cancelled, or a [ExecutionException] if the task completed with an error. 101 | */ 102 | fun get(): V? { 103 | // Acquire the shared lock allowing interruption. 104 | acquireSharedInterruptibly(-1) 105 | return getValue() 106 | } 107 | 108 | /** 109 | * Implementation of the actual value retrieval. Will return the value 110 | * on success, an exception on failure, a cancellation on cancellation, or 111 | * an illegal state if the synchronizer is in an invalid state. 112 | */ 113 | private fun getValue(): V? { 114 | val state = state 115 | when (state) { 116 | COMPLETED -> return if (exception != null) { 117 | throw ExecutionException(exception) 118 | } else { 119 | value 120 | } 121 | CANCELLED, INTERRUPTED -> throw cancellationExceptionWithCause("Task was cancelled.", exception) 122 | else -> throw IllegalStateException("Error, synchronizer in invalid state: $state") 123 | } 124 | } 125 | 126 | private fun cancellationExceptionWithCause(message: String?, cause: Throwable?): CancellationException { 127 | val exception = CancellationException(message) 128 | exception.initCause(cause) 129 | return exception 130 | } 131 | 132 | /** 133 | * Checks if the state is [.INTERRUPTED]. 134 | */ 135 | fun wasInterrupted(): Boolean { 136 | return state == INTERRUPTED 137 | } 138 | 139 | /** 140 | * Transition to the COMPLETED state and set the value. 141 | */ 142 | fun set(v: V?): Boolean { 143 | return complete(v, null, COMPLETED) 144 | } 145 | 146 | /** 147 | * Transition to the COMPLETED state and set the exception. 148 | */ 149 | fun setException(t: Throwable): Boolean { 150 | return complete(null, t, COMPLETED) 151 | } 152 | 153 | /** 154 | * Transition to the CANCELLED or INTERRUPTED state. 155 | */ 156 | fun cancel(interrupt: Boolean): Boolean { 157 | return complete(null, null, if (interrupt) INTERRUPTED else CANCELLED) 158 | } 159 | 160 | /** 161 | * Implementation of completing a task. Either `v` or `t` will 162 | * be set but not both. The `finalState` is the state to change to 163 | * from [.RUNNING]. If the state is not in the RUNNING state we 164 | * return `false` after waiting for the state to be set to a valid 165 | * final state ([.COMPLETED], [.CANCELLED], or [ ][.INTERRUPTED]). 166 | * 167 | * @param v the value to set as the result of the computation. 168 | * @param t the exception to set as the result of the computation. 169 | * @param finalState the state to transition to. 170 | */ 171 | private fun complete(v: V?, t: Throwable?, finalState: Int): Boolean { 172 | val doCompletion = compareAndSetState(RUNNING, COMPLETING) 173 | if (doCompletion) { 174 | // If this thread successfully transitioned to COMPLETING, set the value 175 | // and exception and then release to the final state. 176 | this.value = v 177 | // Don't actually construct a CancellationException until necessary. 178 | this.exception = if (finalState and (CANCELLED or INTERRUPTED) != 0) { 179 | CancellationException("Future.cancel() was called.") 180 | } else { 181 | t 182 | } 183 | releaseShared(finalState) 184 | } else if (state == COMPLETING) { 185 | // If some other thread is currently completing the future, block until 186 | // they are done so we can guarantee completion. 187 | acquireShared(-1) 188 | } 189 | return doCompletion 190 | } 191 | 192 | companion object { 193 | private const val RUNNING: Int = 0 194 | private const val COMPLETING: Int = 1 195 | private const val COMPLETED: Int = 2 196 | private const val CANCELLED: Int = 4 197 | private const val INTERRUPTED: Int = 8 198 | } 199 | } 200 | 201 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_capture_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-hdpi/ic_capture_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_image_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-hdpi/ic_image_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-hdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_capture_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-mdpi/ic_capture_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_image_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-mdpi/ic_image_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-mdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_capture_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xhdpi/ic_capture_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_image_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xhdpi/ic_image_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xhdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_capture_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxhdpi/ic_capture_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_image_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxhdpi/ic_image_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxhdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_capture_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxxhdpi/ic_capture_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_image_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxxhdpi/ic_image_thumbnail.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/drawable-xxxhdpi/ic_switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_capture_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 29 | 30 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/menu/camera_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #212121 4 | #000000 5 | #484848 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Camera2 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.3' 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darylgo/Camera2Sample/05ee166342b71952944e922cab5e5f4088ec2304/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 30 14:33:13 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------