├── 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 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 45 | 50 | 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 | --------------------------------------------------------------------------------