├── LICENSE
├── README.md
├── app
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── mobin
│ │ └── customcamera
│ │ ├── CustomCameraUI.kt
│ │ └── core
│ │ ├── AutoFitTextureView.kt
│ │ ├── Camera2.kt
│ │ ├── CameraPreview.kt
│ │ └── Converters.kt
│ └── res
│ ├── drawable-anydpi
│ ├── ic_back.xml
│ ├── ic_camera.xml
│ ├── ic_camera_rotation.xml
│ ├── ic_flash_auto.xml
│ ├── ic_flash_off.xml
│ └── ic_flash_on.xml
│ ├── drawable-hdpi
│ ├── ic_back.png
│ ├── ic_camera.png
│ ├── ic_camera_rotation.png
│ ├── ic_flash_auto.png
│ ├── ic_flash_off.png
│ └── ic_flash_on.png
│ ├── drawable-mdpi
│ ├── ic_back.png
│ ├── ic_camera.png
│ ├── ic_camera_rotation.png
│ ├── ic_flash_auto.png
│ ├── ic_flash_off.png
│ └── ic_flash_on.png
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable-xhdpi
│ ├── ic_back.png
│ ├── ic_camera.png
│ ├── ic_camera_rotation.png
│ ├── ic_flash_auto.png
│ ├── ic_flash_off.png
│ └── ic_flash_on.png
│ ├── drawable-xxhdpi
│ ├── ic_back.png
│ ├── ic_camera.png
│ ├── ic_camera_rotation.png
│ ├── ic_flash_auto.png
│ ├── ic_flash_off.png
│ └── ic_flash_on.png
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ └── activity_custom_camera_ui.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
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
└── settings.gradle
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mobin Munir
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Custom Camera
2 | :camera:
3 |
4 | Source code for a custom camera activity and modifiable wrapper around android camera 2 API to simplify usage.
5 | It supports flash, returns a picture bitmap which can be saved as JPEG/Bitmap.
6 |
7 | ## Features
8 | - Easy to use.
9 | - Supports flash,photo capture and saving to storage by default.
10 | - Save as JPEG/PNG. (Default JPEG)
11 | - Set Image Quality. (Default Max)
12 | - Set Image save path/directory. (Default Pictures directory)
13 | - Its not a fixed dependency to be included in your project to increase redundancy.
14 | - Its flexible to be converted in any library/SDK or modular form as per your requirement.
15 | - Modifications/Enhancements can be made as required.
16 | - Highly optimized and clean code.
17 | - No Obfuscation Required (Proguard/Dexguard).
18 | - **It would be a part of your project while not implying any 3rd-party involvement.**
19 |
20 |
21 | ## Limitations
22 | - Camera2 API can cause device-specific issues.
23 | - May require handling for specific devices.
24 | - Tested on Lollipop, Oreo and Pie.
25 |
26 |
27 |
28 |
29 |
30 | ### How to use ?
31 |
32 | Just clone the project in Android Studio and run it.
33 |
34 | For details read more on [medium](https://medium.com/@mmobinbutt/camera-2-api-for-android-2dc3168b29a9).
35 |
36 |
37 | ### Usage in Live Android Apps
38 |
39 | :camera: [HideBox](https://play.google.com/store/apps/details?id=com.hidebox.mobileapp)
40 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | generateDebugSources
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | apply plugin: 'kotlin-kapt'
8 |
9 | android {
10 | compileSdkVersion 28
11 | defaultConfig {
12 | applicationId "mobin.customcamera"
13 | minSdkVersion 21
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.0"
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
30 | implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
31 | implementation 'androidx.core:core-ktx:1.1.0-alpha05'
32 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
33 | // testImplementation 'junit:junit:4.12'
34 | // androidTestImplementation 'androidx.test:runner:1.1.2-alpha01'
35 | //androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.2-alpha01'
36 |
37 | // Recycler View
38 | // implementation 'androidx.recyclerview:recyclerview:1.0.0'
39 |
40 | //def lifecycle_version = "2.0.0"
41 |
42 | // ViewModel and LiveData
43 | // implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
44 |
45 | // optional - ReactiveStreams support for LiveData
46 | //implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
47 | // For Kotlin use lifecycle-reactivestreams-ktx
48 |
49 |
50 | // Glide (Image caching and management)
51 | // def glideVersion = "4.8.0"
52 | //kapt "com.github.bumptech.glide:compiler:$glideVersion"
53 | // implementation "com.github.bumptech.glide:glide:$glideVersion"
54 |
55 | // reactive programming for Android
56 | // implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
57 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
58 | }
59 | androidExtensions {
60 | experimental = true
61 | }
62 |
--------------------------------------------------------------------------------
/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 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/mobin/customcamera/CustomCameraUI.kt:
--------------------------------------------------------------------------------
1 | package mobin.customcamera
2 |
3 | import android.Manifest
4 | import android.content.pm.PackageManager
5 | import android.os.Build
6 | import android.os.Bundle
7 | import android.widget.Toast
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.core.content.ContextCompat
10 | import io.reactivex.disposables.Disposable
11 | import kotlinx.android.synthetic.main.activity_custom_camera_ui.*
12 | import mobin.customcamera.core.Camera2
13 | import mobin.customcamera.core.Converters
14 | import mobin.ui.R
15 |
16 | class CustomCameraUI : AppCompatActivity() {
17 |
18 | private lateinit var camera2: Camera2
19 | private var disposable: Disposable? = null
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_custom_camera_ui)
24 | init()
25 | }
26 |
27 | private fun init() {
28 |
29 | if (ContextCompat.checkSelfPermission(
30 | this,
31 | Manifest.permission.CAMERA
32 | ) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
33 | this,
34 | Manifest.permission.WRITE_EXTERNAL_STORAGE
35 | ) == PackageManager.PERMISSION_GRANTED
36 | )
37 |
38 | initCamera2Api()
39 | else {
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
41 | requestPermissions(arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE), 3)
42 | else initCamera2Api()
43 |
44 | }
45 |
46 | }
47 |
48 | private fun initCamera2Api() {
49 |
50 | camera2 = Camera2(this, camera_view)
51 |
52 | iv_rotate_camera.setOnClickListener {
53 | camera2.switchCamera()
54 | }
55 |
56 | iv_capture_image.setOnClickListener { v ->
57 | camera2.takePhoto {
58 | Toast.makeText(v.context, "Saving Picture", Toast.LENGTH_SHORT).show()
59 | disposable = Converters.convertBitmapToFile(it) { file ->
60 | Toast.makeText(v.context, "Saved Picture Path ${file.path}", Toast.LENGTH_SHORT).show()
61 | }
62 |
63 | }
64 |
65 |
66 | }
67 |
68 | iv_camera_flash_on.setOnClickListener {
69 | camera2.setFlash(Camera2.FLASH.ON)
70 | it.alpha = 1f
71 | iv_camera_flash_auto.alpha = 0.4f
72 | iv_camera_flash_off.alpha = 0.4f
73 | }
74 |
75 |
76 | iv_camera_flash_auto.setOnClickListener {
77 | iv_camera_flash_off.alpha = 0.4f
78 | iv_camera_flash_on.alpha = 0.4f
79 | it.alpha = 1f
80 | camera2.setFlash(Camera2.FLASH.AUTO)
81 | }
82 |
83 | iv_camera_flash_off.setOnClickListener {
84 | camera2.setFlash(Camera2.FLASH.OFF)
85 | it.alpha = 1f
86 | iv_camera_flash_on.alpha = 0.4f
87 | iv_camera_flash_auto.alpha = 0.4f
88 |
89 | }
90 |
91 | }
92 |
93 |
94 | override fun onPause() {
95 | // cameraPreview.pauseCamera()
96 | camera2.close()
97 | super.onPause()
98 | }
99 |
100 | override fun onResume() {
101 | // cameraPreview.resumeCamera()
102 | camera2.onResume()
103 | super.onResume()
104 | }
105 |
106 | override fun onDestroy() {
107 | if (disposable != null)
108 | disposable!!.dispose()
109 | super.onDestroy()
110 | }
111 |
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/mobin/customcamera/core/AutoFitTextureView.kt:
--------------------------------------------------------------------------------
1 | package mobin.customcamera.core
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.TextureView
6 |
7 | class AutoFitTextureView : TextureView {
8 | private var mRatioWidth = 0
9 | private var mRatioHeight = 0
10 |
11 | constructor(context: Context) : this(context, null)
12 |
13 | constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
14 |
15 | constructor(context: Context, attributeSet: AttributeSet?, defStyle: Int) : super(context, attributeSet, defStyle)
16 |
17 | /**
18 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
19 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
20 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
21 | *
22 | * @param width Relative horizontal size
23 | * @param height Relative vertical size
24 | */
25 | fun setAspectRatio(width: Int, height: Int) {
26 | if (width < 0 || height < 0) {
27 | throw IllegalArgumentException("Size cannot be negative.")
28 | }
29 | mRatioWidth = width
30 | mRatioHeight = height
31 | requestLayout()
32 | }
33 |
34 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
35 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
36 | val width = MeasureSpec.getSize(widthMeasureSpec)
37 | val height = MeasureSpec.getSize(heightMeasureSpec)
38 | if (0 == mRatioWidth || 0 == mRatioHeight) {
39 | setMeasuredDimension(width, height)
40 | } else {
41 | if (width < height * mRatioWidth / mRatioHeight) {
42 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth)
43 | } else {
44 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height)
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/mobin/customcamera/core/Camera2.kt:
--------------------------------------------------------------------------------
1 | package mobin.customcamera.core
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.Context
6 | import android.content.pm.PackageManager
7 | import android.content.res.Configuration
8 | import android.graphics.*
9 | import android.graphics.Matrix.ScaleToFit
10 | import android.hardware.camera2.*
11 | import android.os.Build
12 | import android.os.Handler
13 | import android.os.HandlerThread
14 | import android.util.Log
15 | import android.util.Size
16 | import android.util.SparseIntArray
17 | import android.view.Surface
18 | import android.view.TextureView
19 | import androidx.core.content.ContextCompat
20 | import java.util.*
21 | import java.util.Collections.singletonList
22 | import kotlin.Comparator
23 |
24 |
25 | class Camera2(private val activity: Activity, private val textureView: AutoFitTextureView) {
26 |
27 | private var onBitmapReady: (Bitmap) -> Unit = {}
28 | private val cameraManager: CameraManager =
29 | textureView.context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
30 | private var cameraFacing = CameraCharacteristics.LENS_FACING_BACK
31 | private var previewSize: Size? = null
32 | //Current Camera id
33 | private var cameraId = "-1"
34 | private var backgroundHandler: Handler? = null
35 | private var backgroundThread: HandlerThread? = null
36 | private var cameraDevice: CameraDevice? = null
37 | //
38 | private var cameraCaptureSession: CameraCaptureSession? = null
39 | // capture request builder for camera.
40 | private var captureRequestBuilder: CaptureRequest.Builder? = null
41 | // capture request generated by above builder.
42 | private var captureRequest: CaptureRequest? = null
43 | private var flash = FLASH.AUTO
44 |
45 | private var cameraState = STATE_PREVIEW
46 |
47 |
48 | private var surface: Surface? = null
49 |
50 |
51 | /**
52 | * Whether the current camera device supports Flash or not.
53 | */
54 | private var isFlashSupported = true
55 |
56 | /**
57 | * Orientation of the camera sensor
58 | */
59 | private var mSensorOrientation = 0
60 |
61 |
62 | private val cameraCaptureCallBack = object : CameraCaptureSession.CaptureCallback() {
63 |
64 | private fun process(captureResult: CaptureResult) {
65 | when (cameraState) {
66 | // We have nothing to do when the camera preview is working normally.
67 | STATE_PREVIEW -> {
68 |
69 | }
70 | STATE_WAITING_LOCK -> {
71 | val afState = captureResult[CaptureResult.CONTROL_AF_STATE]
72 |
73 | if (afState == null) {
74 | captureStillPicture()
75 | } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
76 | // CONTROL_AE_STATE can be null on some devices
77 | val aeState = captureResult[CaptureResult.CONTROL_AE_STATE]
78 | if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
79 | cameraState = STATE_PICTURE_TAKEN
80 | captureStillPicture()
81 |
82 | } else runPrecaptureSequence()
83 | }
84 | }
85 |
86 | STATE_WAITING_PRECAPTURE -> {
87 | // CONTROL_AE_STATE can be null on some devices
88 | val aeState = captureResult[CaptureResult.CONTROL_AE_STATE]
89 | if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
90 | cameraState = STATE_WAITING_NON_PRECAPTURE
91 | }
92 |
93 |
94 | }
95 |
96 | STATE_WAITING_NON_PRECAPTURE -> {
97 | // CONTROL_AE_STATE can be null on some devices
98 | val aeState = captureResult[CaptureResult.CONTROL_AE_STATE]
99 | if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
100 | cameraState = STATE_PICTURE_TAKEN
101 | captureStillPicture()
102 | }
103 | }
104 | }
105 | }
106 |
107 | override fun onCaptureProgressed(
108 | session: CameraCaptureSession,
109 | request: CaptureRequest,
110 | partialResult: CaptureResult
111 | ) {
112 | process(partialResult)
113 | }
114 |
115 | override fun onCaptureCompleted(
116 | session: CameraCaptureSession,
117 | request: CaptureRequest,
118 | result: TotalCaptureResult
119 | ) {
120 | process(result)
121 | }
122 | }
123 |
124 |
125 | private companion object {
126 | // These values represent Camera states.
127 |
128 | // Showing Camera Preview.
129 | private const val STATE_PREVIEW = 0
130 | // Waiting for the focus to be locked.
131 | private const val STATE_WAITING_LOCK = 1
132 | // Waiting for the exposure to be in pre-capture state.
133 | private const val STATE_WAITING_PRECAPTURE = 2
134 | // Waiting for the exposure to be in any other state except pre-capture state.
135 | private const val STATE_WAITING_NON_PRECAPTURE = 3
136 | // Picture was taken
137 | private const val STATE_PICTURE_TAKEN = 4
138 |
139 |
140 | private val ORIENTATIONS = SparseIntArray()
141 |
142 | init {
143 | ORIENTATIONS.append(Surface.ROTATION_0, 90)
144 | ORIENTATIONS.append(Surface.ROTATION_90, 0)
145 | ORIENTATIONS.append(Surface.ROTATION_180, 270)
146 | ORIENTATIONS.append(Surface.ROTATION_270, 180)
147 | }
148 |
149 | /**
150 | * Max preview width that is guaranteed by Camera2 API
151 | */
152 | private const val MAX_PREVIEW_WIDTH = 1920
153 |
154 | /**
155 | * Max preview height that is guaranteed by Camera2 API
156 | */
157 | private const val MAX_PREVIEW_HEIGHT = 1080
158 |
159 |
160 | // Flag to check if camera capture sessions is closed.
161 |
162 | // private var cameraSessionClosed = false
163 |
164 |
165 | /**
166 | * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
167 | * is at least as large as the respective texture view size, and that is at most as large as the
168 | * respective max size, and whose aspect ratio matches with the specified value. If such size
169 | * doesn't exist, choose the largest one that is at most as large as the respective max size,
170 | * and whose aspect ratio matches with the specified value.
171 | *
172 | * @param choices The list of sizes that the camera supports for the intended output
173 | * class
174 | * @param textureViewWidth The width of the texture view relative to sensor coordinate
175 | * @param textureViewHeight The height of the texture view relative to sensor coordinate
176 | * @param maxWidth The maximum width that can be chosen
177 | * @param maxHeight The maximum height that can be chosen
178 | * @param aspectRatio The aspect ratio
179 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough
180 | */
181 | private fun chooseOptimalSize(
182 | choices: Array, textureViewWidth: Int,
183 | textureViewHeight: Int, maxWidth: Int, maxHeight: Int, aspectRatio: Size
184 | ): Size {
185 |
186 | // Collect the supported resolutions that are at least as big as the preview Surface
187 | val bigEnough = arrayListOf()
188 | // Collect the supported resolutions that are smaller than the preview Surface
189 | val notBigEnough = arrayListOf()
190 | val w = aspectRatio.width
191 | val h = aspectRatio.height
192 | for (option in choices) {
193 | if (option.width <= maxWidth && option.height <= maxHeight &&
194 | option.height == option.width * h / w
195 | ) {
196 | if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
197 | bigEnough.add(option)
198 | } else {
199 | notBigEnough.add(option)
200 | }
201 | }
202 | }
203 |
204 | // Pick the smallest of those big enough. If there is no one big enough, pick the
205 | // largest of those not big enough.
206 |
207 | return when {
208 | bigEnough.isNotEmpty() -> Collections.min(bigEnough, compareSizesByArea)
209 | notBigEnough.isNotEmpty() -> Collections.max(notBigEnough, compareSizesByArea)
210 | else -> {
211 | Log.e("Camera", "Couldn't find any suitable preview size")
212 | choices[0]
213 | }
214 | }
215 | }
216 |
217 | private val compareSizesByArea = Comparator { lhs, rhs ->
218 | // We cast here to ensure the multiplications won't overflow
219 | java.lang.Long.signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height)
220 | }
221 |
222 | }
223 |
224 |
225 | // private fun configureTransform() {
226 | //
227 | // }
228 |
229 | private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
230 | override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
231 | configureTransform(width, height)
232 |
233 | }
234 |
235 | override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
236 |
237 | }
238 |
239 | override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
240 | return true
241 | }
242 |
243 | override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
244 | openCamera(width, height)
245 |
246 | }
247 | }
248 |
249 | private val cameraStateCallback = object : CameraDevice.StateCallback() {
250 | override fun onOpened(camera: CameraDevice) {
251 | this@Camera2.cameraDevice = camera
252 | createPreviewSession()
253 | }
254 |
255 | override fun onDisconnected(camera: CameraDevice) {
256 | camera.close()
257 | this@Camera2.cameraDevice = null
258 | }
259 |
260 | override fun onError(camera: CameraDevice, error: Int) {
261 | }
262 | }
263 |
264 | fun onResume() {
265 | openBackgroundThread()
266 | if (textureView.isAvailable) {
267 | openCamera(textureView.width, textureView.height)
268 | } else textureView.surfaceTextureListener = surfaceTextureListener
269 |
270 |
271 | }
272 |
273 | fun close() {
274 | closeCamera()
275 | closeBackgroundThread()
276 |
277 | }
278 |
279 |
280 | private fun closeCamera() {
281 | if (cameraCaptureSession != null) {
282 | cameraCaptureSession!!.close()
283 | cameraCaptureSession = null
284 | // cameraSessionClosed = true
285 | }
286 |
287 | if (cameraDevice != null) {
288 | cameraDevice!!.close()
289 | cameraDevice = null
290 | }
291 | }
292 |
293 | private fun closeBackgroundThread() {
294 | if (backgroundHandler != null) {
295 | backgroundThread!!.quitSafely()
296 | backgroundThread = null
297 | backgroundHandler = null
298 | }
299 | }
300 |
301 |
302 | private fun openCamera(width: Int, height: Int) {
303 | if (ContextCompat.checkSelfPermission(
304 | textureView.context,
305 | Manifest.permission.CAMERA
306 | ) == PackageManager.PERMISSION_GRANTED
307 | ) {
308 | //setup camera called here
309 | setUpCameraOutputs(width, height)
310 | configureTransform(width, height)
311 |
312 | cameraManager.openCamera(cameraId, cameraStateCallback, backgroundHandler)
313 |
314 |
315 | } else Log.e("Camera2", "Requires Camera Permission")
316 | }
317 |
318 | /**
319 | * Sets up member variables related to camera.
320 | *
321 | * @param width The width of available size for camera preview
322 | * @param height The height of available size for camera preview
323 | */
324 | private fun setUpCameraOutputs(width: Int, height: Int) {
325 | try {
326 | for (cameraId in cameraManager.cameraIdList) {
327 | val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)
328 | val cameraFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
329 |
330 | if (cameraFacing == this.cameraFacing) {
331 | val streamConfigurationMap = cameraCharacteristics.get(
332 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
333 | )
334 | // For still image captures, we use the largest available size.
335 | val largest = Collections.max(
336 | streamConfigurationMap?.getOutputSizes(ImageFormat.JPEG)?.toList(),
337 | compareSizesByArea
338 | )
339 | // image reader could go here
340 |
341 | // Find out if we need to swap dimension to get the preview size relative to sensor
342 | // coordinate.
343 | val displayRotation = activity.windowManager.defaultDisplay.rotation
344 |
345 | //noinspection ConstantConditions
346 | mSensorOrientation = cameraCharacteristics[CameraCharacteristics.SENSOR_ORIENTATION] ?: 0
347 |
348 | var swappedDimensions = false
349 |
350 | when (displayRotation) {
351 | Surface.ROTATION_0 -> {
352 | }
353 | Surface.ROTATION_90 -> {
354 | }
355 | Surface.ROTATION_180 -> {
356 | swappedDimensions = mSensorOrientation == 90 || mSensorOrientation == 270
357 | }
358 | Surface.ROTATION_270 -> {
359 | swappedDimensions = mSensorOrientation == 0 || mSensorOrientation == 180
360 | }
361 | else -> Log.e("Camera2", "Display rotation is invalid: $displayRotation")
362 |
363 | }
364 |
365 | val displaySize = Point()
366 |
367 | activity.windowManager.defaultDisplay.getSize(displaySize)
368 |
369 | var rotatedPreviewWidth = width
370 | var rotatedPreviewHeight = height
371 | var maxPreviewWidth = displaySize.x
372 | var maxPreviewHeight = displaySize.y
373 |
374 | if (swappedDimensions) {
375 | rotatedPreviewWidth = height
376 | rotatedPreviewHeight = width
377 | maxPreviewWidth = displaySize.y
378 | maxPreviewHeight = displaySize.x
379 | }
380 |
381 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
382 | maxPreviewWidth = MAX_PREVIEW_WIDTH
383 | }
384 |
385 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
386 | maxPreviewHeight = MAX_PREVIEW_HEIGHT
387 | }
388 |
389 |
390 | // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
391 | // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
392 | // garbage capture data.
393 | previewSize = chooseOptimalSize(
394 | streamConfigurationMap!!.getOutputSizes(SurfaceTexture::class.java),
395 | rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
396 | maxPreviewHeight, largest
397 | )
398 |
399 | // We fit the aspect ratio of TextureView to the size of preview we picked.
400 | val orientation = activity.resources.configuration.orientation
401 |
402 | if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
403 | textureView.setAspectRatio(
404 | previewSize!!.width, previewSize!!.height
405 | )
406 | } else {
407 | textureView.setAspectRatio(
408 | previewSize!!.height, previewSize!!.width
409 | )
410 | }
411 | // check flash support
412 | val flashSupported = cameraCharacteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
413 | isFlashSupported = flashSupported == null ?: false
414 |
415 | this.cameraId = cameraId
416 |
417 | return
418 | }
419 | }
420 | } catch (e: CameraAccessException) {
421 | e.printStackTrace()
422 | }
423 | }
424 |
425 | /**
426 | * Configures the necessary [android.graphics.Matrix] transformation to `mTextureView`.
427 | * This method should be called after the camera preview size is determined in
428 | * setUpCameraOutputs and also the size of `mTextureView` is fixed.
429 | *
430 | * @param viewWidth The width of `mTextureView`
431 | * @param viewHeight The height of `mTextureView`
432 | */
433 | private fun configureTransform(viewWidth: Int, viewHeight: Int) {
434 | val rotation = activity.windowManager.defaultDisplay.rotation
435 | val matrix = Matrix()
436 | val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
437 | val bufferRect = RectF(0f, 0f, previewSize!!.height.toFloat(), previewSize!!.width.toFloat())
438 | val centerX = viewRect.centerX()
439 | val centerY = viewRect.centerY()
440 | if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
441 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
442 | matrix.setRectToRect(viewRect, bufferRect, ScaleToFit.FILL)
443 | val scale = Math.max(
444 | viewHeight.toFloat() / previewSize!!.height,
445 | viewWidth.toFloat() / previewSize!!.width
446 | )
447 | matrix.postScale(scale, scale, centerX, centerY)
448 | matrix.postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
449 | } else if (Surface.ROTATION_180 == rotation) {
450 | matrix.postRotate(180f, centerX, centerY)
451 | }
452 | textureView.setTransform(matrix)
453 | }
454 |
455 | /**
456 | * Retrieves the JPEG orientation from the specified screen rotation.
457 | *
458 | * @param rotation The screen rotation.
459 | * @return The JPEG orientation (one of 0, 90, 270, and 360)
460 | */
461 | private fun getOrientation(rotation: Int) =
462 |
463 | // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
464 | // We have to take that into account and rotate JPEG properly.
465 | // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
466 | // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
467 | (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360
468 |
469 |
470 | private fun openBackgroundThread() {
471 | backgroundThread = HandlerThread("camera_background_thread")
472 | backgroundThread!!.start()
473 | backgroundHandler = Handler(backgroundThread!!.looper)
474 | }
475 |
476 | // Creates a new camera preview session
477 | private fun createPreviewSession() {
478 |
479 | try {
480 |
481 | val surfaceTexture = textureView.surfaceTexture
482 | // We configure the size of default buffer to be the size of camera preview we want.
483 | surfaceTexture.setDefaultBufferSize(previewSize!!.width, previewSize!!.height)
484 |
485 | // This is the output Surface we need to start preview.
486 | if (surface == null)
487 | surface = Surface(surfaceTexture)
488 |
489 | val previewSurface = surface
490 | // We set up a CaptureRequest.Builder with the output Surface.
491 |
492 | captureRequestBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
493 | captureRequestBuilder!!.addTarget(previewSurface!!)
494 |
495 |
496 | // Here, we create a CameraCaptureSession for camera preview.
497 |
498 | cameraDevice!!.createCaptureSession(
499 | singletonList(previewSurface),
500 | object : CameraCaptureSession.StateCallback() {
501 |
502 | override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
503 | if (cameraDevice == null) {
504 | return
505 | }
506 |
507 | try {
508 | // When session is ready we start displaying preview.
509 | this@Camera2.cameraCaptureSession = cameraCaptureSession
510 | // cameraSessionClosed = false
511 |
512 | // Auto focus should be continuous for camera preview.
513 |
514 | captureRequestBuilder!!.set(
515 | CaptureRequest.CONTROL_AF_MODE,
516 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
517 | )
518 |
519 | // Finally, we start displaying the camera preview.
520 | captureRequest = captureRequestBuilder!!.build()
521 |
522 | this@Camera2.cameraCaptureSession!!.setRepeatingRequest(
523 | captureRequest!!,
524 | cameraCaptureCallBack,
525 | backgroundHandler
526 | )
527 |
528 |
529 | /* Initially flash is automatically enabled when necessary. But In case activity is resumed and flash is set to fire
530 | we set flash after the preview request is processed to ensure flash fires only during a still capture. */
531 | setFlashMode(captureRequestBuilder!!, true)
532 |
533 |
534 | } catch (e: CameraAccessException) {
535 | e.printStackTrace()
536 | }
537 |
538 | }
539 |
540 | override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
541 |
542 | }
543 | }, backgroundHandler
544 | )
545 | } catch (e: CameraAccessException) {
546 | e.printStackTrace()
547 | }
548 |
549 | }
550 |
551 | /* For some reason, The code for firing flash in both methods below which is prescribed doesn't work on API level below PIE it maybe a device-specific issue as very common with Camera API
552 | so I had to build my own code if the else block works well for your devices even below PIE I would recommend using it because that's
553 | the official way and code is available for all levels >=21 as mentioned.
554 | */
555 |
556 | private fun flashOn(captureRequestBuilder: CaptureRequest.Builder) {
557 | // cameraManager.setTorchMode()
558 | if (Build.VERSION.SDK_INT > 28) {
559 | captureRequestBuilder.set(
560 | CaptureRequest.FLASH_MODE,
561 | CaptureRequest.FLASH_MODE_TORCH
562 | )
563 | } else {
564 | captureRequestBuilder.set(
565 | CaptureRequest.CONTROL_AE_MODE,
566 | CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
567 | )
568 | }
569 |
570 | }
571 |
572 | // sets flash mode for a capture request builder
573 | private fun setFlashMode(
574 | captureRequestBuilder: CaptureRequest.Builder,
575 | trigger: Boolean
576 | ) {
577 | if (trigger) {
578 | // This is how to tell the camera to trigger.
579 | captureRequestBuilder.set(
580 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
581 | CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START
582 | )
583 | }
584 | when (flash) {
585 | FLASH.ON -> flashOn(captureRequestBuilder)
586 | FLASH.AUTO -> {
587 | captureRequestBuilder.set(
588 | CaptureRequest.CONTROL_AE_MODE,
589 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
590 | )
591 | }
592 | else -> captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF)
593 | }
594 | }
595 |
596 | enum class FLASH {
597 | ON, OFF, AUTO
598 | }
599 |
600 | // Locks the preview focus.
601 | private fun lockPreview() {
602 | try {
603 |
604 | captureRequestBuilder!!.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
605 | // Tell #cameraCaptureCallback to wait for the lock.
606 | cameraState = STATE_WAITING_LOCK
607 | cameraCaptureSession!!.capture(
608 | captureRequestBuilder!!.build(), cameraCaptureCallBack, backgroundHandler
609 | )
610 | } catch (e: Exception) {
611 | e.printStackTrace()
612 | }
613 |
614 | }
615 |
616 |
617 | private fun unlockPreview() {
618 | try {
619 | // Reset the auto-focus trigger
620 | captureRequestBuilder!!.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
621 |
622 | setFlashMode(captureRequestBuilder!!, false)
623 |
624 |
625 | cameraCaptureSession!!.capture(
626 | captureRequestBuilder!!.build(), cameraCaptureCallBack, backgroundHandler
627 | )
628 | // After this, the camera will go back to the normal state of preview.
629 | cameraState = STATE_PREVIEW
630 |
631 |
632 |
633 | cameraCaptureSession!!.setRepeatingRequest(
634 | captureRequest!!, cameraCaptureCallBack,
635 | backgroundHandler
636 | )
637 |
638 |
639 | } catch (e: CameraAccessException) {
640 | e.printStackTrace()
641 | }
642 |
643 | }
644 |
645 | // This method switches Camera Lens Front or Back then restarts camera.
646 | fun switchCamera() {
647 | close()
648 | cameraFacing = if (cameraFacing == CameraCharacteristics.LENS_FACING_BACK)
649 | CameraCharacteristics.LENS_FACING_FRONT
650 | else CameraCharacteristics.LENS_FACING_BACK
651 | onResume()
652 |
653 |
654 | }
655 |
656 |
657 | fun setFlash(flash: FLASH) {
658 |
659 | this.flash = flash
660 |
661 | if (textureView.context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
662 | when (cameraFacing) {
663 | // CameraCharacteristics.LENS_FACING_BACK -> setFlashMode()
664 | CameraCharacteristics.LENS_FACING_FRONT -> Log.e("Camera2", "Front Camera Flash isn't supported yet.")
665 | }
666 | }
667 |
668 | }
669 |
670 | /**
671 | * Run the precapture sequence for capturing a still image. This method should be called when
672 | * we get a response in {@link #mCaptureCallback} from {@link #lockPreview()}.
673 | */
674 |
675 | private fun runPrecaptureSequence() {
676 | try {
677 |
678 |
679 | // Tell #cameraCaptureCallback to wait for the precapture sequence to be set.
680 | cameraState = STATE_WAITING_PRECAPTURE
681 |
682 | setFlashMode(captureRequestBuilder!!, true)
683 |
684 | cameraCaptureSession!!.capture(captureRequestBuilder!!.build(), cameraCaptureCallBack, backgroundHandler)
685 |
686 |
687 | } catch (e: CameraAccessException) {
688 | e.printStackTrace()
689 | }
690 |
691 |
692 | }
693 |
694 |
695 | private fun captureBitmap() {
696 | if (textureView.isAvailable) {
697 | onBitmapReady(textureView.bitmap)
698 | }
699 | }
700 |
701 | /**
702 | * Capture a still picture. This method should be called when we get a response in
703 | * {@link #cameraCaptureCallback} from both {@link #lockPreview()}.
704 | */
705 | private fun captureStillPicture() {
706 | try {
707 | // This is the CaptureRequest.Builder that we use to take a picture.
708 | val captureBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
709 |
710 | // val surfaceTexture = textureView.surfaceTexture
711 | // surfaceTexture.setDefaultBufferSize(previewSize!!.width, previewSize!!.height)
712 | captureBuilder.addTarget(surface!!)
713 | // Use the same AE and AF modes as the preview.
714 | captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
715 |
716 |
717 | setFlashMode(captureBuilder, true)
718 |
719 | // Orientation
720 | val rotation = activity.windowManager.defaultDisplay.rotation
721 |
722 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation))
723 |
724 | cameraCaptureSession!!.stopRepeating()
725 | cameraCaptureSession!!.abortCaptures()
726 | cameraCaptureSession!!.capture(captureBuilder.build(), object : CameraCaptureSession.CaptureCallback() {
727 | override fun onCaptureCompleted(
728 | session: CameraCaptureSession,
729 | request: CaptureRequest,
730 | result: TotalCaptureResult
731 | ) {
732 |
733 | captureBitmap()
734 | unlockPreview()
735 |
736 |
737 | }
738 | }, null)
739 |
740 |
741 | } catch (e: CameraAccessException) {
742 | e.printStackTrace()
743 | }
744 |
745 |
746 | }
747 | // uncomment to use
748 |
749 | // fun isFlashAuto() =
750 | // isFlashSupported && flash == FLASH.AUTO && cameraFacing == CameraCharacteristics.LENS_FACING_BACK
751 | //
752 | //
753 | // fun isFlashON() =
754 | // isFlashSupported && (flash == FLASH.ON) && cameraFacing == CameraCharacteristics.LENS_FACING_BACK
755 |
756 |
757 | fun takePhoto(onBitmapReady: (Bitmap) -> Unit) {
758 |
759 | this.onBitmapReady = onBitmapReady
760 | lockPreview()
761 | }
762 |
763 | }
764 |
765 |
766 |
767 |
768 |
769 |
770 |
--------------------------------------------------------------------------------
/app/src/main/java/mobin/customcamera/core/CameraPreview.kt:
--------------------------------------------------------------------------------
1 | package mobin.customcamera.core
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.BitmapFactory
6 | import android.hardware.Camera
7 | import android.util.Log
8 | import android.view.SurfaceHolder
9 | import android.view.SurfaceView
10 | import android.view.View
11 |
12 | @Deprecated("used when given support below api 21")
13 | class CameraPreview(context: Context) : SurfaceView(context), SurfaceHolder.Callback {
14 |
15 |
16 | private var onPictureListener: (Bitmap) -> Unit = {}
17 |
18 | private val pictureCallback = Camera.PictureCallback { data, camera ->
19 | val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
20 | onPictureListener(bitmap)
21 |
22 |
23 | }
24 |
25 | fun setPictureListener(onPictureListener: (Bitmap) -> Unit) {
26 | this.onPictureListener = onPictureListener
27 | }
28 |
29 | private var camera: Camera? = null
30 | private var cameraFront = false
31 | private var previewSize: Camera.Size? = null
32 |
33 | init {
34 | camera = Camera.open()
35 | camera!!.setDisplayOrientation(90)
36 |
37 |
38 | holder.addCallback(this)
39 | }
40 |
41 |
42 | private fun findFrontFacingCamera(): Int {
43 |
44 | var cameraId = -1
45 | // Search for the front facing camera
46 | val numberOfCameras = Camera.getNumberOfCameras()
47 | for (i in 0 until numberOfCameras) {
48 | val info = Camera.CameraInfo()
49 | Camera.getCameraInfo(i, info)
50 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
51 | cameraId = i
52 | cameraFront = true
53 | break
54 | }
55 | }
56 | return cameraId
57 |
58 | }
59 |
60 | // private fun setCameraDisplayOrientation(activity: AppCompatActivity, cameraId: Int) {
61 | // val info = android.hardware.Camera.CameraInfo()
62 | // android.hardware.Camera.getCameraInfo(cameraId, info)
63 | // val rotation = activity.getWindowManager().getDefaultDisplay()
64 | // .getRotation()
65 | // var degrees = 0
66 | // when (rotation) {
67 | // Surface.ROTATION_0 -> degrees = 0
68 | // Surface.ROTATION_90 -> degrees = 90
69 | // Surface.ROTATION_180 -> degrees = 180
70 | // Surface.ROTATION_270 -> degrees = 270
71 | // }
72 | //
73 | // var result: Int
74 | // if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
75 | // result = (info.orientation + degrees) % 360;
76 | // result = (360 - result) % 360; // compensate the mirror
77 | // } else { // back-facing
78 | // result = (info.orientation - degrees + 360) % 360;
79 | // }
80 | // camera!!.setDisplayOrientation(result)
81 | // }
82 |
83 | private fun refreshCamera() {
84 | if (holder.surface == null) {
85 | // preview surface does not exist
86 | return
87 | }
88 | // stop preview before making changes
89 | try {
90 | camera!!.stopPreview()
91 | } catch (e: Exception) {
92 | // ignore: tried to stop a non-existent preview
93 | }
94 |
95 | // set preview size and make any resize, rotate or
96 | // reformatting changes here
97 | // start preview with new settings
98 | //setCamera(camera)
99 | try {
100 | val params = camera!!.parameters
101 | params.setPreviewSize(previewSize!!.width, previewSize!!.height)
102 | camera!!.parameters = params
103 | camera!!.setPreviewDisplay(holder)
104 | camera!!.startPreview()
105 | } catch (e: Exception) {
106 | Log.e("CustomCam", "Error starting camera preview: " + e.message)
107 | }
108 |
109 | }
110 |
111 | private fun findBackFacingCamera(): Int {
112 | var cameraId = -1
113 | //Search for the back facing camera
114 | //get the number of cameras
115 | val numberOfCameras = Camera.getNumberOfCameras()
116 | //for every camera check
117 | for (i in 0 until numberOfCameras) {
118 | val info = Camera.CameraInfo()
119 | Camera.getCameraInfo(i, info)
120 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
121 | cameraId = i
122 | cameraFront = false
123 | break
124 |
125 | }
126 |
127 | }
128 | return cameraId
129 | }
130 |
131 | override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
132 | // If your preview can change or rotate, take care of those events here.
133 | // Make sure to stop the preview before resizing or reformatting it.
134 |
135 | refreshCamera()
136 | }
137 |
138 | override fun surfaceDestroyed(holder: SurfaceHolder) {
139 | releaseCamera()
140 | }
141 |
142 | override fun surfaceCreated(holder: SurfaceHolder) {
143 | camera!!.setPreviewDisplay(holder)
144 | camera!!.startPreview()
145 | }
146 |
147 | fun switchCamera() {
148 | val cameras = Camera.getNumberOfCameras()
149 | if (cameras > 1) {
150 | releaseCamera()
151 | chooseCamera()
152 | }
153 |
154 | }
155 |
156 | fun takePhoto() {
157 | camera!!.takePicture(null, null, pictureCallback)
158 | }
159 |
160 | private fun chooseCamera() {
161 | //if the camera preview is the front
162 | if (cameraFront) {
163 | val cameraId = findBackFacingCamera()
164 | if (cameraId >= 0) {
165 | //open the backFacingCamera
166 | //set a picture callback
167 | //refresh the preview
168 |
169 | camera = Camera.open(cameraId)
170 | camera!!.setDisplayOrientation(90)
171 | refreshCamera()
172 | }
173 | } else {
174 | val cameraId = findFrontFacingCamera()
175 | if (cameraId >= 0) {
176 | //open the backFacingCamera
177 | //set a picture callback
178 | //refresh the preview
179 | camera = Camera.open(cameraId)
180 | camera!!.setDisplayOrientation(90)
181 | refreshCamera()
182 | }
183 | }
184 | }
185 |
186 | fun pauseCamera() {
187 | releaseCamera()
188 | }
189 |
190 | fun resumeCamera() {
191 | if (camera == null) {
192 | camera = Camera.open()
193 | camera!!.setDisplayOrientation(90)
194 | refreshCamera()
195 | }
196 | }
197 |
198 | private fun releaseCamera() {
199 | // stop and release camera
200 | if (camera != null) {
201 | camera?.stopPreview()
202 | camera?.setPreviewCallback(null)
203 | camera?.release()
204 | camera = null
205 | }
206 | }
207 |
208 | private fun getOptimalPreviewSize(sizes: List?, w: Int, h: Int): Camera.Size? {
209 | val ASPECT_TOLERANCE = 0.1
210 | val targetRatio = h.toDouble() / w
211 |
212 | if (sizes == null) return null
213 |
214 | var optimalSize: Camera.Size? = null
215 | var minDiff = java.lang.Double.MAX_VALUE
216 |
217 | for (size in sizes) {
218 | val ratio = size.width.toDouble() / size.height
219 | if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue
220 | if (Math.abs(size.height - h) < minDiff) {
221 | optimalSize = size
222 | minDiff = Math.abs(size.height - h).toDouble()
223 | }
224 | }
225 |
226 | if (optimalSize == null) {
227 | minDiff = java.lang.Double.MAX_VALUE
228 | for (size in sizes) {
229 | if (Math.abs(size.height - h) < minDiff) {
230 | optimalSize = size
231 | minDiff = Math.abs(size.height - h).toDouble()
232 | }
233 | }
234 | }
235 | return optimalSize
236 | }
237 |
238 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
239 | val width = View.resolveSize(suggestedMinimumWidth, widthMeasureSpec)
240 | val height = View.resolveSize(suggestedMinimumWidth, heightMeasureSpec)
241 | setMeasuredDimension(width, height)
242 | val supportedPreviewSizes = camera!!.parameters.supportedPreviewSizes
243 | if (supportedPreviewSizes != null)
244 | previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height)
245 |
246 | }
247 | }
--------------------------------------------------------------------------------
/app/src/main/java/mobin/customcamera/core/Converters.kt:
--------------------------------------------------------------------------------
1 | package mobin.customcamera.core
2 |
3 | import android.graphics.Bitmap
4 | import android.os.Environment
5 | import android.util.Log
6 | import io.reactivex.Single
7 | import io.reactivex.android.schedulers.AndroidSchedulers
8 | import io.reactivex.disposables.Disposable
9 | import io.reactivex.schedulers.Schedulers
10 | import java.io.ByteArrayOutputStream
11 | import java.io.File
12 | import java.io.FileOutputStream
13 | import java.io.IOException
14 |
15 | object Converters {
16 |
17 |
18 | // This subscription needs to be disposed off to release the system resources primarily held for purpose.
19 |
20 | @JvmStatic // this annotation is required for caller class written in Java to recognize this method as static
21 | fun convertBitmapToFile(bitmap: Bitmap, onBitmapConverted: (File) -> Unit): Disposable {
22 | return Single.fromCallable {
23 | compressBitmap(bitmap)
24 | }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
25 | .subscribe({
26 | if (it != null) {
27 | Log.i("convertedPicturePath", it.path)
28 | onBitmapConverted(it)
29 | }
30 | }, { it.printStackTrace() })
31 | }
32 |
33 | private fun compressBitmap(bitmap: Bitmap): File? {
34 | //create a file to write bitmap data
35 |
36 | try {
37 | val myStuff =
38 | File(
39 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
40 | "Android Custom Camera"
41 | )
42 | if (!myStuff.exists())
43 | myStuff.mkdirs()
44 | val picture = File(myStuff, "Mobin-" + System.currentTimeMillis() + ".jpeg")
45 |
46 | //Convert bitmap to byte array
47 | val bos = ByteArrayOutputStream()
48 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100 /*ignored for PNG*/, bos)
49 | val bitmapData = bos.toByteArray()
50 |
51 | //write the bytes in file
52 | val fos = FileOutputStream(picture)
53 | fos.write(bitmapData)
54 | fos.flush()
55 | fos.close()
56 | return picture
57 | } catch (e: IOException) {
58 | e.printStackTrace()
59 | }
60 |
61 | return null
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_back.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_camera_rotation.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_flash_auto.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_flash_off.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_flash_on.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_camera_rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_camera_rotation.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_flash_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_flash_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_flash_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-hdpi/ic_flash_on.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_camera_rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_camera_rotation.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_flash_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_flash_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_flash_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-mdpi/ic_flash_on.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_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_camera_rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_camera_rotation.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_flash_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_flash_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_flash_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xhdpi/ic_flash_on.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_back.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_camera.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_camera_rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_camera_rotation.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_flash_auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_flash_auto.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_flash_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_flash_off.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_flash_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/drawable-xxhdpi/ic_flash_on.png
--------------------------------------------------------------------------------
/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_custom_camera_ui.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
18 |
25 |
26 |
27 |
37 |
38 |
48 |
49 |
59 |
60 |
61 |
62 |
63 |
70 |
71 |
81 |
82 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/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/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mmobin789/Android-Custom-Camera/e7580c6376e7617320294313d554d5bf8229ccc1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 8dp
6 | 120dp
7 | 16dp
8 |
9 |
10 | 13sp
11 | 85dp
12 | 14sp
13 | 15sp
14 | 16sp
15 | 17sp
16 | 20sp
17 |
18 |
19 | 5dp
20 | 360dp
21 |
22 | 16dp
23 |
24 | 23sp
25 | 16sp
26 | 15sp
27 | 15sp
28 | 17sp
29 | 23.3sp
30 | 14sp
31 | 18sp
32 | 15sp
33 | 12sp
34 | 9sp
35 | 24sp
36 | 0.5dp
37 |
38 | 36sp
39 |
40 | 4dp
41 | 8dp
42 | 12dp
43 | 16dp
44 |
45 | 80dp
46 | 200dp
47 | 80dp
48 |
49 | 18dp
50 | 15dp
51 | 32dp
52 | 256dp
53 |
54 | 10dp
55 |
56 | 50dp
57 |
58 | 20dp
59 |
60 | 2dp
61 |
62 | 80dp
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Android Custom Camera
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.31'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.4.0'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 |
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/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 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------