├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── play.png │ │ │ │ ├── stop.png │ │ │ │ ├── record.png │ │ │ │ ├── light_off.png │ │ │ │ └── light_on.png │ │ │ ├── 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 │ │ │ ├── xml │ │ │ │ └── provider_paths.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── item_media.xml │ │ │ │ ├── dialog_simple_picker.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── activity_video_record.xml │ │ │ ├── drawable │ │ │ │ ├── ic_flash_on.xml │ │ │ │ ├── ic_flash_off.xml │ │ │ │ ├── ic_flash_auto.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ └── drawable-v24 │ │ │ │ ├── ic_switch_camera.xml │ │ │ │ ├── ic_camera.xml │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── top │ │ │ │ └── defaults │ │ │ │ └── cameraapp │ │ │ │ ├── options │ │ │ │ ├── PickerItemWrapper.java │ │ │ │ ├── SizeItem.java │ │ │ │ ├── AspectRatioItem.java │ │ │ │ └── Commons.java │ │ │ │ ├── CameraApplication.java │ │ │ │ ├── GridViewItem.java │ │ │ │ ├── dialog │ │ │ │ ├── PickerDialog.java │ │ │ │ └── SimplePickerDialog.java │ │ │ │ ├── MainActivity.java │ │ │ │ └── PhotographerActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── top │ │ │ └── defaults │ │ │ ├── videoapp │ │ │ └── ExampleUnitTest.java │ │ │ └── cameraapp │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── top │ │ └── defaults │ │ ├── videoapp │ │ └── ExampleInstrumentedTest.java │ │ └── cameraapp │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── camera ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── top │ │ │ │ └── defaults │ │ │ │ └── camera │ │ │ │ ├── InternalPhotographer.java │ │ │ │ ├── Keys.java │ │ │ │ ├── Values.java │ │ │ │ ├── PhotographerFactory.java │ │ │ │ ├── PhotographerHelper.java │ │ │ │ ├── SimpleOnEventListener.java │ │ │ │ ├── CanvasDrawer.java │ │ │ │ ├── ImageSaver.java │ │ │ │ ├── Error.java │ │ │ │ ├── Size.java │ │ │ │ ├── SizeMap.java │ │ │ │ ├── ImageCaptureCallback.java │ │ │ │ ├── DisplayOrientationDetector.java │ │ │ │ ├── CallbackHandler.java │ │ │ │ ├── FocusHandler.java │ │ │ │ ├── Photographer.java │ │ │ │ ├── CameraViewOverlay.java │ │ │ │ ├── AspectRatio.java │ │ │ │ ├── CameraView.java │ │ │ │ ├── AutoFitTextureView.java │ │ │ │ └── Utils.java │ │ └── res │ │ │ └── values │ │ │ └── top_defaults_camera_attrs.xml │ ├── test │ │ └── java │ │ │ └── top │ │ │ └── defaults │ │ │ └── camera │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── top │ │ └── defaults │ │ └── camera │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro ├── installv1.gradle ├── build.gradle └── bintrayv1.gradle ├── settings.gradle ├── art ├── screenshot1.jpg └── screenshot2.jpg ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── markdown-navigator │ └── profiles_settings.xml ├── encodings.xml ├── vcs.xml ├── findbugs-idea.xml ├── modules.xml ├── compiler.xml ├── runConfigurations.xml ├── codeStyles │ └── Project.xml └── misc.xml ├── gradle.properties ├── .gitignore ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /camera/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':camera' 2 | -------------------------------------------------------------------------------- /art/screenshot1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/art/screenshot1.jpg -------------------------------------------------------------------------------- /art/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/art/screenshot2.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/drawable-xxxhdpi/play.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/drawable-xxxhdpi/stop.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/drawable-xxxhdpi/record.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/light_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/drawable-xxxhdpi/light_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/light_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/drawable-xxxhdpi/light_on.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /camera/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duanhong169/Camera/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/InternalPhotographer.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.app.Activity; 4 | 5 | interface InternalPhotographer extends Photographer { 6 | 7 | void initWithViewfinder(Activity activity, CameraView preview); 8 | } 9 | -------------------------------------------------------------------------------- /.idea/findbugs-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 10 15:44:41 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Keys.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | public interface Keys { 4 | 5 | String MODE = "mode"; 6 | String ASPECT_RATIO = "aspect.ratio"; 7 | String AUTO_FOCUS = "auto.focus"; 8 | String FACING = "facing"; 9 | String FLASH = "flash"; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/options/PickerItemWrapper.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.options; 2 | 3 | import top.defaults.view.PickerView; 4 | 5 | public interface PickerItemWrapper extends PickerView.PickerItem { 6 | 7 | T get(); 8 | 9 | interface WrapperFactory> { 10 | 11 | W create(T item); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Values.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | public interface Values { 4 | boolean DEBUG = false; 5 | 6 | AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3); 7 | 8 | int MODE_IMAGE = 0; 9 | int MODE_VIDEO = 1; 10 | 11 | int FLASH_OFF = 0; 12 | int FLASH_ON = 1; 13 | int FLASH_TORCH = 2; 14 | int FLASH_AUTO = 3; 15 | int FLASH_RED_EYE = 4; 16 | 17 | int FACING_BACK = 0; 18 | int FACING_FRONT = 1; 19 | } 20 | -------------------------------------------------------------------------------- /app/src/test/java/top/defaults/videoapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.videoapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /camera/src/test/java/top/defaults/camera/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/test/java/top/defaults/cameraapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/PhotographerFactory.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.app.Activity; 4 | 5 | public class PhotographerFactory { 6 | 7 | public static Photographer createPhotographerWithCamera2(Activity activity, CameraView preview) { 8 | InternalPhotographer photographer = new Camera2Photographer(); 9 | photographer.initWithViewfinder(activity, preview); 10 | preview.assign(photographer); 11 | return photographer; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/options/SizeItem.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.options; 2 | 3 | import top.defaults.camera.Size; 4 | 5 | public class SizeItem implements PickerItemWrapper { 6 | 7 | private Size size; 8 | 9 | public SizeItem(Size size) { 10 | this.size = size; 11 | } 12 | 13 | @Override 14 | public String getText() { 15 | return size.getWidth() + " * " + size.getHeight(); 16 | } 17 | 18 | @Override 19 | public Size get() { 20 | return size; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/options/AspectRatioItem.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.options; 2 | 3 | import top.defaults.camera.AspectRatio; 4 | 5 | public class AspectRatioItem implements PickerItemWrapper { 6 | 7 | private AspectRatio aspectRatio; 8 | 9 | public AspectRatioItem(AspectRatio ratio) { 10 | aspectRatio = ratio; 11 | } 12 | 13 | @Override 14 | public String getText() { 15 | return aspectRatio.toString(); 16 | } 17 | 18 | @Override 19 | public AspectRatio get() { 20 | return aspectRatio; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/CameraApplication.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import android.app.Application; 4 | 5 | import timber.log.Timber; 6 | import top.defaults.view.TextButton; 7 | import top.defaults.view.TextButtonEffect; 8 | 9 | public class CameraApplication extends Application { 10 | 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | Timber.plant(new Timber.DebugTree()); 15 | TextButton.Defaults defaults = TextButton.Defaults.get(); 16 | defaults.set(top.defaults.view.textbutton.R.styleable.TextButton_backgroundEffect, TextButtonEffect.BACKGROUND_EFFECT_RIPPLE); 17 | defaults.set(top.defaults.view.textbutton.R.styleable.TextButton_rippleColor, 0xffff0000); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/PhotographerHelper.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | public class PhotographerHelper { 4 | 5 | private Photographer photographer; 6 | 7 | public PhotographerHelper(Photographer photographer) { 8 | this.photographer = photographer; 9 | } 10 | 11 | public void switchMode() { 12 | int newMode = (photographer.getMode() == Values.MODE_IMAGE ? Values.MODE_VIDEO : Values.MODE_IMAGE); 13 | photographer.setMode(newMode); 14 | } 15 | 16 | public void flip() { 17 | int newFacing = (photographer.getFacing() == Values.FACING_BACK ? Values.FACING_FRONT : Values.FACING_BACK); 18 | photographer.setFacing(newFacing); 19 | } 20 | 21 | public void setFileDir(String fileDir) { 22 | Utils.setFileDir(fileDir); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/GridViewItem.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | public class GridViewItem extends android.support.v7.widget.AppCompatImageView { 7 | 8 | public GridViewItem(Context context) { 9 | super(context); 10 | } 11 | 12 | public GridViewItem(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | public GridViewItem(Context context, AttributeSet attrs, int defStyle) { 17 | super(context, attrs, defStyle); 18 | } 19 | 20 | @Override 21 | public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 22 | //noinspection SuspiciousNameCombination 23 | super.onMeasure(widthMeasureSpec, widthMeasureSpec); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /camera/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 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/SimpleOnEventListener.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | public class SimpleOnEventListener implements Photographer.OnEventListener { 4 | @Override 5 | public void onDeviceConfigured() { 6 | 7 | } 8 | 9 | @Override 10 | public void onPreviewStarted() { 11 | 12 | } 13 | 14 | @Override 15 | public void onZoomChanged(float zoom) { 16 | 17 | } 18 | 19 | @Override 20 | public void onPreviewStopped() { 21 | 22 | } 23 | 24 | @Override 25 | public void onStartRecording() { 26 | 27 | } 28 | 29 | @Override 30 | public void onFinishRecording(String filePath) { 31 | 32 | } 33 | 34 | @Override 35 | public void onShotFinished(String filePath) { 36 | 37 | } 38 | 39 | @Override 40 | public void onError(Error error) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_media.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/androidTest/java/top/defaults/videoapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.videoapp; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("top.defaults.videoapp", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /camera/src/androidTest/java/top/defaults/camera/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("top.defaults.video.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/androidTest/java/top/defaults/cameraapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("top.defaults.videoapp", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/CanvasDrawer.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.Point; 7 | 8 | public interface CanvasDrawer { 9 | 10 | Paint[] initPaints(); 11 | 12 | void draw(Canvas canvas, Point point, Paint[] paints); 13 | 14 | class DefaultCanvasDrawer implements CanvasDrawer { 15 | 16 | @Override 17 | public Paint[] initPaints() { 18 | Paint focusPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 19 | focusPaint.setStyle(Paint.Style.STROKE); 20 | focusPaint.setStrokeWidth(2); 21 | focusPaint.setColor(Color.WHITE); 22 | return new Paint[]{ focusPaint }; 23 | } 24 | 25 | @Override 26 | public void draw(Canvas canvas, Point point, Paint[] paints) { 27 | if (paints == null || paints.length == 0) return; 28 | canvas.drawCircle(point.x, point.y, 150, paints[0]); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flash_on.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/options/Commons.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.options; 2 | 3 | import android.os.Environment; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | public class Commons { 10 | 11 | public static final String MEDIA_DIR = Environment.getExternalStorageDirectory().getPath() + "/0/dev/CameraApp"; 12 | 13 | public static > List wrapItems(Collection items, PickerItemWrapper.WrapperFactory factory) { 14 | List wrappedItems = new ArrayList<>(); 15 | for (T item: items) { 16 | wrappedItems.add(factory.create(item)); 17 | } 18 | return wrappedItems; 19 | } 20 | 21 | public static > T findEqual(Collection items, U target) { 22 | if (items == null || target == null) return null; 23 | for (T item : items) { 24 | if (item.get().equals(target)) { 25 | return item; 26 | } 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /camera/src/main/res/values/top_defaults_camera_attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flash_off.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flash_auto.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CameraApp 3 | Open Camera 4 | Gallery 5 | No enough permissions(CAMERA, RECORD_AUDIO, WRITE_EXTERNAL_STORAGE) 6 | Recording 7 | Record 8 | Shot 9 | Cancel 10 | Image Mode 11 | Video Mode 12 | Finish 13 | Flip 14 | Play[%s] 15 | Done 16 | Fill 17 | Zoom 18 | Video size 19 | Image size 20 | Ratio 21 | Auto 22 | On 23 | Off 24 | 25 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/ImageSaver.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.media.Image; 4 | 5 | import java.io.FileOutputStream; 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | 9 | class ImageSaver implements Runnable { 10 | 11 | private final Image image; 12 | private final String filePath; 13 | 14 | ImageSaver(Image image, String filePath) { 15 | this.image = image; 16 | this.filePath = filePath; 17 | } 18 | 19 | @Override 20 | public void run() { 21 | ByteBuffer buffer = image.getPlanes()[0].getBuffer(); 22 | byte[] bytes = new byte[buffer.remaining()]; 23 | buffer.get(bytes); 24 | FileOutputStream output = null; 25 | try { 26 | output = new FileOutputStream(filePath); 27 | output.write(bytes); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } finally { 31 | image.close(); 32 | if (null != output) { 33 | try { 34 | output.close(); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_switch_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/dictionaries 41 | .idea/libraries 42 | .idea/caches 43 | 44 | # Keystore files 45 | # Uncomment the following line if you do not want to check your keystore files in. 46 | #*.jks 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Google Services (e.g. APIs or Firebase) 52 | google-services.json 53 | 54 | # Freeline 55 | freeline.py 56 | freeline/ 57 | freeline_project_description.json 58 | 59 | # fastlane 60 | fastlane/report.xml 61 | fastlane/Preview.html 62 | fastlane/screenshots 63 | fastlane/test_output 64 | fastlane/readme.md 65 | -------------------------------------------------------------------------------- /camera/installv1.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.github.dcendents.android-maven' 2 | 3 | group = publishedGroupId // Maven Group ID for the artifact 4 | 5 | install { 6 | repositories.mavenInstaller { 7 | // This generates POM.xml with proper parameters 8 | pom { 9 | project { 10 | packaging 'aar' 11 | groupId publishedGroupId 12 | artifactId artifact 13 | 14 | // Add your description here 15 | name libraryName 16 | description libraryDescription 17 | url siteUrl 18 | 19 | // Set your license 20 | licenses { 21 | license { 22 | name licenseName 23 | url licenseUrl 24 | } 25 | } 26 | developers { 27 | developer { 28 | id developerId 29 | name developerName 30 | email developerEmail 31 | } 32 | } 33 | scm { 34 | connection gitUrl 35 | developerConnection gitUrl 36 | url siteUrl 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 19 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/dialog/PickerDialog.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.dialog; 2 | 3 | import android.app.Dialog; 4 | import android.app.DialogFragment; 5 | import android.content.DialogInterface; 6 | import android.graphics.Color; 7 | import android.os.Bundle; 8 | import android.view.View; 9 | import android.view.Window; 10 | import android.view.WindowManager; 11 | 12 | import top.defaults.view.PickerView; 13 | 14 | public abstract class PickerDialog extends DialogFragment { 15 | 16 | public abstract T getSelectedItem(Class cls); 17 | 18 | public interface ActionListener { 19 | 20 | void onCancelClick(PickerDialog dialog); 21 | 22 | void onDoneClick(PickerDialog dialog); 23 | } 24 | 25 | @Override 26 | public Dialog onCreateDialog(Bundle savedInstanceState) { 27 | Dialog dialog = super.onCreateDialog(savedInstanceState); 28 | Window window = dialog.getWindow(); 29 | if (window != null) { 30 | window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); 31 | View decorView = window.getDecorView(); 32 | decorView.setSystemUiVisibility(getActivity().getWindow().getDecorView().getSystemUiVisibility()); 33 | dialog.setOnShowListener(dialog1 -> window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)); 34 | } 35 | return dialog; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_simple_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 20 | 21 | 28 | 29 | 30 | 34 | 35 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 21 | 22 | 27 | 28 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /camera/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | bintrayRepo = 'Hong' 5 | bintrayName = 'camera' 6 | 7 | publishedGroupId = 'com.github.duanhong169' 8 | libraryName = 'Camera' 9 | artifact = 'camera' 10 | 11 | libraryDescription = 'Use android camera to take pictures and videos' 12 | 13 | siteUrl = 'https://github.com/duanhong169/Camera' 14 | gitUrl = 'https://github.com/duanhong169/Camera.git' 15 | 16 | libraryVersion = '1.0.3' 17 | 18 | developerId = 'duanhong169' 19 | developerName = 'Hong Duan' 20 | developerEmail = 'duanhong169@gmail.com' 21 | 22 | licenseName = 'The MIT License, Version 2.0' 23 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 24 | allLicenses = ["Apache-2.0"] 25 | } 26 | 27 | android { 28 | compileSdkVersion 27 29 | defaultConfig { 30 | minSdkVersion 21 31 | targetSdkVersion 27 32 | versionCode 1 33 | versionName "1.0" 34 | 35 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 36 | 37 | } 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | buildTypes { 43 | release { 44 | minifyEnabled false 45 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 46 | } 47 | } 48 | 49 | } 50 | 51 | dependencies { 52 | implementation fileTree(dir: 'libs', include: ['*.jar']) 53 | 54 | api 'com.github.duanhong169:logger:1.0.0' 55 | implementation 'com.android.support:appcompat-v7:27.1.1' 56 | testImplementation 'junit:junit:4.12' 57 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 58 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 59 | } 60 | 61 | apply from: 'installv1.gradle' 62 | apply from: 'bintrayv1.gradle' -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 39 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "top.defaults.cameraapp" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(include: ['*.jar'], dir: 'libs') 27 | implementation 'com.android.support:appcompat-v7:27.1.1' 28 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 29 | implementation 'com.android.support:design:27.1.1' 30 | implementation 'com.jakewharton:butterknife:8.8.1' 31 | implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' 32 | implementation 'com.jakewharton.timber:timber:4.6.0' 33 | implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.5@aar' 34 | implementation 'com.github.duanhong169:picker-view:1.0.0' 35 | implementation 'com.github.duanhong169:text-button:1.0.5' 36 | // implementation 'com.github.duanhong169:camera:1.0.3' 37 | implementation project(':camera') 38 | implementation 'com.github.bumptech.glide:glide:4.7.1' 39 | annotationProcessor 'com.github.bumptech.glide:compiler:4.7.1' 40 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 41 | testImplementation 'junit:junit:4.12' 42 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 43 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 44 | } 45 | -------------------------------------------------------------------------------- /camera/bintrayv1.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | 3 | version = libraryVersion 4 | 5 | if (project.hasProperty("android")) { // Android libraries 6 | task sourcesJar(type: Jar) { 7 | classifier = 'sources' 8 | from android.sourceSets.main.java.srcDirs 9 | } 10 | 11 | task javadoc(type: Javadoc) { 12 | source = android.sourceSets.main.java.srcDirs 13 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 14 | failOnError false 15 | } 16 | } else { // Java libraries 17 | task sourcesJar(type: Jar, dependsOn: classes) { 18 | classifier = 'sources' 19 | from sourceSets.main.allSource 20 | } 21 | } 22 | 23 | task javadocJar(type: Jar, dependsOn: javadoc) { 24 | classifier = 'javadoc' 25 | from javadoc.destinationDir 26 | } 27 | 28 | artifacts { 29 | archives javadocJar 30 | archives sourcesJar 31 | } 32 | 33 | bintray { 34 | // Bintray 35 | Properties properties = new Properties() 36 | File localPropertiesFile = project.rootProject.file('local.properties') 37 | if (localPropertiesFile.exists()) { 38 | properties.load(localPropertiesFile.newDataInputStream()) 39 | } 40 | 41 | user = properties.getProperty("bintray.user") 42 | key = properties.getProperty("bintray.apikey") 43 | 44 | configurations = ['archives'] 45 | pkg { 46 | repo = bintrayRepo 47 | name = bintrayName 48 | desc = libraryDescription 49 | websiteUrl = siteUrl 50 | vcsUrl = gitUrl 51 | licenses = allLicenses 52 | publish = true 53 | publicDownloadNumbers = true 54 | version { 55 | desc = libraryDescription 56 | gpg { 57 | sign = true // Determines whether to GPG sign the files. The default is false 58 | passphrase = properties.getProperty("bintray.gpg.password") // Optional. The passphrase for GPG signing 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Error.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import java.util.Locale; 4 | 5 | public class Error extends java.lang.Error { 6 | public static final int ERROR_DEFAULT_CODE = -1; 7 | public static final int ERROR_CAMERA = 1; 8 | public static final int ERROR_UNSUPPORTED_OPERATION = 2; 9 | public static final int ERROR_PERMISSION = 3; 10 | public static final int ERROR_STORAGE = 4; 11 | public static final int ERROR_INVALID_PARAM = 5; 12 | 13 | private int code; 14 | private Throwable cause; 15 | 16 | Error(int code) { 17 | super(messageFor(code)); 18 | this.code = code; 19 | } 20 | 21 | Error(int code, Throwable cause) { 22 | super(cause.getMessage()); 23 | this.code = code; 24 | this.cause = cause; 25 | cause.printStackTrace(); 26 | } 27 | 28 | Error(int code, String message) { 29 | super(message); 30 | this.code = code; 31 | } 32 | 33 | Error(int code, String message, Throwable cause) { 34 | super(message); 35 | this.code = code; 36 | this.cause = cause; 37 | cause.printStackTrace(); 38 | } 39 | 40 | @Override 41 | public Throwable getCause() { 42 | return cause; 43 | } 44 | 45 | private static String messageFor(int code) { 46 | String message; 47 | switch (code) { 48 | case ERROR_CAMERA: 49 | message = "Camera error"; 50 | break; 51 | case ERROR_UNSUPPORTED_OPERATION: 52 | message = "Unsupported operation"; 53 | break; 54 | case ERROR_PERMISSION: 55 | message = "No enough permissions"; 56 | break; 57 | case ERROR_STORAGE: 58 | message = "No enough storage"; 59 | break; 60 | default: 61 | message = "Undefined error"; 62 | break; 63 | } 64 | return message; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return String.format(Locale.getDefault(), "%s(%d)", getMessage(), code); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/dialog/SimplePickerDialog.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp.dialog; 2 | 3 | import android.content.DialogInterface; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import java.util.List; 11 | 12 | import timber.log.Timber; 13 | import top.defaults.cameraapp.R; 14 | import top.defaults.view.PickerView; 15 | 16 | public class SimplePickerDialog extends PickerDialog { 17 | 18 | private ActionListener actionListener; 19 | private PickerView pickerView; 20 | private List items; 21 | private T initialItem; 22 | 23 | public static SimplePickerDialog create(ActionListener actionListener) { 24 | SimplePickerDialog dialog = new SimplePickerDialog<>(); 25 | dialog.actionListener = actionListener; 26 | return dialog; 27 | } 28 | 29 | public void setItems(List items) { 30 | this.items = items; 31 | } 32 | 33 | public void setInitialItem(T item) { 34 | initialItem = item; 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { 40 | View view = inflater.inflate(R.layout.dialog_simple_picker, container, false); 41 | 42 | pickerView = view.findViewById(R.id.pickerView); 43 | pickerView.setItems(items, null); 44 | pickerView.setSelectedItemPosition(items.indexOf(initialItem)); 45 | 46 | attachActions(view.findViewById(R.id.done), view.findViewById(R.id.cancel)); 47 | return view; 48 | } 49 | 50 | @Override 51 | public void onCancel(DialogInterface dialog) { 52 | if (actionListener != null) { 53 | actionListener.onCancelClick(this); 54 | } 55 | } 56 | 57 | private void attachActions(View done, View cancel) { 58 | cancel.setOnClickListener(v -> { 59 | if (actionListener != null) { 60 | actionListener.onCancelClick(this); 61 | } 62 | dismiss(); 63 | }); 64 | done.setOnClickListener(v -> { 65 | if (actionListener != null) { 66 | actionListener.onDoneClick(this); 67 | } 68 | dismiss(); 69 | }); 70 | } 71 | 72 | @Override 73 | public T getSelectedItem(Class cls) { 74 | return pickerView.getSelectedItem(cls); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Size.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package top.defaults.camera; 18 | 19 | import android.support.annotation.NonNull; 20 | 21 | /** 22 | * Immutable class for describing width and height dimensions in pixels. 23 | */ 24 | public class Size implements Comparable { 25 | 26 | private final int width; 27 | private final int height; 28 | 29 | /** 30 | * Create a new immutable Size instance. 31 | * 32 | * @param width The width of the size, in pixels 33 | * @param height The height of the size, in pixels 34 | */ 35 | Size(int width, int height) { 36 | this.width = width; 37 | this.height = height; 38 | } 39 | 40 | public int getWidth() { 41 | return width; 42 | } 43 | 44 | public int getHeight() { 45 | return height; 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (o == null) { 51 | return false; 52 | } 53 | if (this == o) { 54 | return true; 55 | } 56 | if (o instanceof Size) { 57 | Size size = (Size) o; 58 | return width == size.width && height == size.height; 59 | } 60 | return false; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return width + "x" + height; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 71 | return height ^ ((width << (Integer.SIZE / 2)) | (width >>> (Integer.SIZE / 2))); 72 | } 73 | 74 | @Override 75 | public int compareTo(@NonNull Size another) { 76 | int area = getAreaSize(); 77 | int anotherArea = another.getAreaSize(); 78 | if (area != anotherArea) { 79 | return area - anotherArea; 80 | } else { 81 | return width - another.width; 82 | } 83 | } 84 | 85 | int getAreaSize() { 86 | return width * height; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/SizeMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package top.defaults.camera; 18 | 19 | import android.support.v4.util.ArrayMap; 20 | 21 | import java.util.Collections; 22 | import java.util.Set; 23 | import java.util.SortedSet; 24 | import java.util.TreeSet; 25 | 26 | /** 27 | * A collection class that automatically groups {@link Size}s by their {@link AspectRatio}s. 28 | */ 29 | class SizeMap { 30 | 31 | private final ArrayMap> mRatios = new ArrayMap<>(); 32 | 33 | /** 34 | * Add a new {@link Size} to this collection. 35 | * 36 | * @param size The size to add. 37 | * @return {@code true} if it is added, {@code false} if it already exists and is not added. 38 | */ 39 | public boolean add(Size size) { 40 | for (AspectRatio ratio : mRatios.keySet()) { 41 | if (ratio.matches(size)) { 42 | final SortedSet sizes = mRatios.get(ratio); 43 | if (sizes.contains(size)) { 44 | return false; 45 | } else { 46 | sizes.add(size); 47 | return true; 48 | } 49 | } 50 | } 51 | // None of the existing ratio matches the provided size; add a new key 52 | SortedSet sizes = new TreeSet<>(); 53 | sizes.add(size); 54 | mRatios.put(AspectRatio.of(size.getWidth(), size.getHeight()), sizes); 55 | return true; 56 | } 57 | 58 | /** 59 | * Removes the specified aspect ratio and all sizes associated with it. 60 | * 61 | * @param ratio The aspect ratio to be removed. 62 | */ 63 | public void remove(AspectRatio ratio) { 64 | mRatios.remove(ratio); 65 | } 66 | 67 | Set ratios() { 68 | return mRatios.keySet(); 69 | } 70 | 71 | SortedSet sizes(AspectRatio ratio) { 72 | return mRatios.get(ratio); 73 | } 74 | 75 | Size defaultSize() { 76 | return mRatios.get(AspectRatio.parse("4:3")).last(); 77 | } 78 | 79 | void clear() { 80 | mRatios.clear(); 81 | } 82 | 83 | boolean isEmpty() { 84 | return mRatios.isEmpty(); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/ImageCaptureCallback.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.hardware.camera2.CameraCaptureSession; 4 | import android.hardware.camera2.CaptureRequest; 5 | import android.hardware.camera2.CaptureResult; 6 | import android.hardware.camera2.TotalCaptureResult; 7 | import android.support.annotation.NonNull; 8 | 9 | /** 10 | * A {@link CameraCaptureSession.CaptureCallback} for capturing a still picture. 11 | */ 12 | abstract class ImageCaptureCallback 13 | extends CameraCaptureSession.CaptureCallback { 14 | 15 | static final int STATE_PREVIEW = 0; 16 | static final int STATE_LOCKING = 1; 17 | static final int STATE_LOCKED = 2; 18 | static final int STATE_PRECAPTURE = 3; 19 | static final int STATE_WAITING = 4; 20 | static final int STATE_CAPTURING = 5; 21 | 22 | private int state; 23 | 24 | ImageCaptureCallback() { 25 | } 26 | 27 | void setState(int state) { 28 | this.state = state; 29 | } 30 | 31 | @Override 32 | public void onCaptureProgressed(@NonNull CameraCaptureSession session, 33 | @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { 34 | process(partialResult); 35 | } 36 | 37 | @Override 38 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 39 | @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { 40 | process(result); 41 | } 42 | 43 | private void process(@NonNull CaptureResult result) { 44 | switch (state) { 45 | case STATE_LOCKING: { 46 | Integer af = result.get(CaptureResult.CONTROL_AF_STATE); 47 | if (af == null) { 48 | break; 49 | } 50 | if (af == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || 51 | af == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { 52 | Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); 53 | if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 54 | setState(STATE_CAPTURING); 55 | onReady(); 56 | } else { 57 | setState(STATE_LOCKED); 58 | onPrecaptureRequired(); 59 | } 60 | } 61 | break; 62 | } 63 | case STATE_PRECAPTURE: { 64 | Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); 65 | if (ae == null || ae == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || 66 | ae == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED || 67 | ae == CaptureResult.CONTROL_AE_STATE_CONVERGED) { 68 | setState(STATE_WAITING); 69 | } 70 | break; 71 | } 72 | case STATE_WAITING: { 73 | Integer ae = result.get(CaptureResult.CONTROL_AE_STATE); 74 | if (ae == null || ae != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { 75 | setState(STATE_CAPTURING); 76 | onReady(); 77 | } 78 | break; 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * Called when it is ready to take a still picture. 85 | */ 86 | abstract void onReady(); 87 | 88 | /** 89 | * Called when it is necessary to run the precapture sequence. 90 | */ 91 | abstract void onPrecaptureRequired(); 92 | 93 | } 94 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/DisplayOrientationDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package top.defaults.camera; 18 | 19 | import android.content.Context; 20 | import android.util.SparseIntArray; 21 | import android.view.Display; 22 | import android.view.OrientationEventListener; 23 | import android.view.Surface; 24 | 25 | 26 | /** 27 | * Monitors the value returned from {@link Display#getRotation()}. 28 | */ 29 | abstract class DisplayOrientationDetector { 30 | 31 | private final OrientationEventListener orientationEventListener; 32 | 33 | /** Mapping from Surface.Rotation_n to degrees. */ 34 | private static final SparseIntArray DISPLAY_ORIENTATIONS = new SparseIntArray(); 35 | 36 | static { 37 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_0, 0); 38 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_90, 90); 39 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_180, 180); 40 | DISPLAY_ORIENTATIONS.put(Surface.ROTATION_270, 270); 41 | } 42 | 43 | private Display display; 44 | 45 | private int lastKnownDisplayOrientation = 0; 46 | 47 | public DisplayOrientationDetector(Context context) { 48 | orientationEventListener = new OrientationEventListener(context) { 49 | 50 | /** This is either Surface.Rotation_0, _90, _180, _270, or -1 (invalid). */ 51 | private int lastKnownRotation = -1; 52 | 53 | @Override 54 | public void onOrientationChanged(int orientation) { 55 | if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN || 56 | display == null) { 57 | return; 58 | } 59 | final int rotation = display.getRotation(); 60 | if (lastKnownRotation != rotation) { 61 | lastKnownRotation = rotation; 62 | dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(rotation)); 63 | } 64 | } 65 | }; 66 | } 67 | 68 | public void enable(Display display) { 69 | this.display = display; 70 | orientationEventListener.enable(); 71 | // Immediately dispatch the first callback 72 | dispatchOnDisplayOrientationChanged(DISPLAY_ORIENTATIONS.get(display.getRotation())); 73 | } 74 | 75 | public void disable() { 76 | orientationEventListener.disable(); 77 | display = null; 78 | } 79 | 80 | public int getLastKnownDisplayOrientation() { 81 | return lastKnownDisplayOrientation; 82 | } 83 | 84 | private void dispatchOnDisplayOrientationChanged(int displayOrientation) { 85 | lastKnownDisplayOrientation = displayOrientation; 86 | onDisplayOrientationChanged(displayOrientation); 87 | } 88 | 89 | /** 90 | * Called when display orientation is changed. 91 | * 92 | * @param displayOrientation One of 0, 90, 180, and 270. 93 | */ 94 | abstract void onDisplayOrientationChanged(int displayOrientation); 95 | 96 | } 97 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/CallbackHandler.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | 7 | import top.defaults.logger.Logger; 8 | 9 | import static top.defaults.camera.Values.DEBUG; 10 | 11 | class CallbackHandler extends Handler { 12 | 13 | private static final int CALLBACK_ON_DEVICE_CONFIGURED = 1; 14 | private static final int CALLBACK_ON_PREVIEW_STARTED = 2; 15 | private static final int CALLBACK_ON_ZOOM_CHANGED = 3; 16 | private static final int CALLBACK_ON_PREVIEW_STOPPED = 4; 17 | private static final int CALLBACK_ON_START_RECORDING = 5; 18 | private static final int CALLBACK_ON_FINISH_RECORDING = 6; 19 | private static final int CALLBACK_ON_SHOT_FINISHED = 7; 20 | private static final int CALLBACK_ON_ERROR = 8; 21 | 22 | private Photographer.OnEventListener onEventListener; 23 | 24 | CallbackHandler(Context context) { 25 | super(context.getMainLooper()); 26 | } 27 | 28 | void setOnEventListener(Photographer.OnEventListener listener) { 29 | onEventListener = listener; 30 | } 31 | 32 | @Override 33 | public void handleMessage(Message msg) { 34 | if (onEventListener == null) { 35 | return; 36 | } 37 | if (DEBUG) { 38 | Logger.d("handleMessage: " + msg.what); 39 | } 40 | switch (msg.what) { 41 | case CALLBACK_ON_DEVICE_CONFIGURED: 42 | onEventListener.onDeviceConfigured(); 43 | break; 44 | case CALLBACK_ON_PREVIEW_STARTED: 45 | onEventListener.onPreviewStarted(); 46 | break; 47 | case CALLBACK_ON_ZOOM_CHANGED: 48 | onEventListener.onZoomChanged((float) msg.obj); 49 | break; 50 | case CALLBACK_ON_PREVIEW_STOPPED: 51 | onEventListener.onPreviewStopped(); 52 | break; 53 | case CALLBACK_ON_START_RECORDING: 54 | onEventListener.onStartRecording(); 55 | break; 56 | case CALLBACK_ON_FINISH_RECORDING: 57 | onEventListener.onFinishRecording((String) msg.obj); 58 | break; 59 | case CALLBACK_ON_SHOT_FINISHED: 60 | onEventListener.onShotFinished((String) msg.obj); 61 | break; 62 | case CALLBACK_ON_ERROR: 63 | onEventListener.onError((Error) msg.obj); 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | void onDeviceConfigured() { 71 | Message.obtain(this, CALLBACK_ON_DEVICE_CONFIGURED).sendToTarget(); 72 | } 73 | 74 | void onPreviewStarted() { 75 | Message.obtain(this, CALLBACK_ON_PREVIEW_STARTED).sendToTarget(); 76 | } 77 | 78 | void onZoomChanged(float zoom) { 79 | Message.obtain(this, CALLBACK_ON_ZOOM_CHANGED, zoom).sendToTarget(); 80 | } 81 | 82 | void onPreviewStopped() { 83 | Message.obtain(this, CALLBACK_ON_PREVIEW_STOPPED).sendToTarget(); 84 | } 85 | 86 | void onStartRecording() { 87 | Message.obtain(this, CALLBACK_ON_START_RECORDING).sendToTarget(); 88 | } 89 | 90 | void onFinishRecording(String filePath) { 91 | Message.obtain(this, CALLBACK_ON_FINISH_RECORDING, filePath).sendToTarget(); 92 | } 93 | 94 | void onShotFinished(String filePath) { 95 | Message.obtain(this, CALLBACK_ON_SHOT_FINISHED, filePath).sendToTarget(); 96 | } 97 | 98 | void onError(final Error error) { 99 | Message.obtain(this, CALLBACK_ON_ERROR, error).sendToTarget(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/FocusHandler.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.graphics.Rect; 4 | import android.hardware.camera2.CameraAccessException; 5 | import android.hardware.camera2.CameraCaptureSession; 6 | import android.hardware.camera2.CameraMetadata; 7 | import android.hardware.camera2.CaptureFailure; 8 | import android.hardware.camera2.CaptureRequest; 9 | import android.hardware.camera2.TotalCaptureResult; 10 | import android.hardware.camera2.params.MeteringRectangle; 11 | import android.support.annotation.NonNull; 12 | 13 | class FocusHandler { 14 | 15 | private boolean isFocusProcessing; 16 | 17 | void focus(CameraCaptureSession captureSession, CaptureRequest.Builder captureRequestBuilder, 18 | Rect focusArea, Callback callback) { 19 | if (isFocusProcessing) { 20 | return; 21 | } 22 | 23 | try { 24 | captureSession.stopRepeating(); 25 | } catch (CameraAccessException e) { 26 | callback.onFinish(new Error(Error.ERROR_CAMERA, e)); 27 | return; 28 | } 29 | 30 | // cancel any existing AF trigger (repeated touches, etc.) 31 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); 32 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); 33 | 34 | // add a new AF trigger with focus region 35 | if (focusArea != null) { 36 | MeteringRectangle rectangle = new MeteringRectangle(focusArea, MeteringRectangle.METERING_WEIGHT_MAX - 1); 37 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{rectangle}); 38 | } 39 | captureRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); 40 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); 41 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); 42 | captureRequestBuilder.setTag("FOCUS_TAG"); // we'll capture this later for resuming the preview 43 | 44 | try { 45 | CameraCaptureSession.CaptureCallback focusCallbackHandler = new CameraCaptureSession.CaptureCallback() { 46 | 47 | @Override 48 | public void onCaptureCompleted(@NonNull CameraCaptureSession session, 49 | @NonNull CaptureRequest request, 50 | @NonNull TotalCaptureResult result) { 51 | isFocusProcessing = false; 52 | 53 | if (request.getTag() == "FOCUS_TAG") { 54 | // the focus trigger is complete, clear AF trigger 55 | captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, null); 56 | callback.onFinish(null); 57 | } 58 | } 59 | 60 | @Override 61 | public void onCaptureFailed(@NonNull CameraCaptureSession session, 62 | @NonNull CaptureRequest request, 63 | @NonNull CaptureFailure failure) { 64 | isFocusProcessing = false; 65 | callback.onFinish(new Error(Error.ERROR_CAMERA, "focus failed")); 66 | } 67 | }; 68 | captureSession.capture(captureRequestBuilder.build(), focusCallbackHandler, null); 69 | } catch (CameraAccessException e) { 70 | e.printStackTrace(); 71 | callback.onFinish(new Error(Error.ERROR_CAMERA, e)); 72 | return; 73 | } 74 | isFocusProcessing = true; 75 | } 76 | 77 | interface Callback { 78 | void onFinish(Error error); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Photographer.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.media.MediaRecorder; 4 | import android.support.annotation.Nullable; 5 | 6 | import java.util.Set; 7 | 8 | public interface Photographer { 9 | 10 | Set getSupportedImageSizes(); 11 | 12 | Set getSupportedVideoSizes(); 13 | 14 | void startPreview(); 15 | 16 | void restartPreview(); 17 | 18 | void stopPreview(); 19 | 20 | Size getPreviewSize(); 21 | 22 | Size getImageSize(); 23 | 24 | void setImageSize(Size size); 25 | 26 | Size getVideoSize(); 27 | 28 | void setVideoSize(Size size); 29 | 30 | Set getSupportedAspectRatios(); 31 | 32 | void setAspectRatio(AspectRatio ratio); 33 | 34 | AspectRatio getAspectRatio(); 35 | 36 | void setAutoFocus(boolean autoFocus); 37 | 38 | boolean getAutoFocus(); 39 | 40 | void setFacing(int facing); 41 | 42 | int getFacing(); 43 | 44 | void setFlash(int flash); 45 | 46 | int getFlash(); 47 | 48 | void setZoom(float zoom); 49 | 50 | float getZoom(); 51 | 52 | void setMode(int mode); 53 | 54 | int getMode(); 55 | 56 | void takePicture(); 57 | 58 | void startRecording(@Nullable MediaRecorderConfigurator configurator); 59 | 60 | /** 61 | * Only works when API level >= 24 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N). 62 | */ 63 | void pauseRecording(); 64 | 65 | /** 66 | * Only works when API level >= 24 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N). 67 | */ 68 | void resumeRecording(); 69 | 70 | void finishRecording(); 71 | 72 | interface MediaRecorderConfigurator { 73 | 74 | /** 75 | * Our Photographer's MediaRecorder use the default configs below: 76 | *
 77 |          * mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 78 |          * mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
 79 |          * mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
 80 |          * if (nextVideoAbsolutePath == null || nextVideoAbsolutePath.isEmpty()) {
 81 |          *    nextVideoAbsolutePath = getVideoFilePath(activityContext); // random file
 82 |          * }
 83 |          * mediaRecorder.setOutputFile(nextVideoAbsolutePath);
 84 |          * mediaRecorder.setVideoEncodingBitRate(10000000);
 85 |          * mediaRecorder.setVideoFrameRate(30);
 86 |          * mediaRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
 87 |          * mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
 88 |          * mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
 89 |          * 
90 | * 91 | * If you need to configure the MediaRecorder by yourself, please be carefully otherwise 92 | * {@link IllegalStateException} may be thrown. See javadoc for {@link MediaRecorder} 93 | * 94 | * @return A boolean value which indicates if we use the default configs for MediaRecorder. 95 | */ 96 | boolean useDefaultConfigs(); 97 | 98 | /** 99 | * Your configurations here may override the default configs when possible. 100 | * 101 | * If you need to configure the MediaRecorder by yourself, please be carefully otherwise 102 | * {@link IllegalStateException} may be thrown. See javadoc for {@link MediaRecorder} 103 | * 104 | * @param recorder The recorder to be configured. 105 | */ 106 | void configure(MediaRecorder recorder); 107 | } 108 | 109 | void setOnEventListener(OnEventListener listener); 110 | 111 | interface OnEventListener { 112 | 113 | void onDeviceConfigured(); 114 | 115 | void onPreviewStarted(); 116 | 117 | void onZoomChanged(float zoom); 118 | 119 | void onPreviewStopped(); 120 | 121 | void onStartRecording(); 122 | 123 | void onFinishRecording(String filePath); 124 | 125 | void onShotFinished(String filePath); 126 | 127 | void onError(Error error); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/CameraViewOverlay.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ArgbEvaluator; 6 | import android.animation.ValueAnimator; 7 | import android.content.Context; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Paint; 11 | import android.graphics.PixelFormat; 12 | import android.graphics.Point; 13 | import android.graphics.PorterDuff; 14 | import android.util.AttributeSet; 15 | import android.view.SurfaceHolder; 16 | import android.view.SurfaceView; 17 | import android.view.animation.DecelerateInterpolator; 18 | 19 | class CameraViewOverlay extends SurfaceView { 20 | 21 | private SurfaceHolder holder; 22 | private Point focusPoint; 23 | private CanvasDrawer canvasDrawer; 24 | private Paint[] paints; 25 | 26 | public CameraViewOverlay(Context context) { 27 | this(context, null); 28 | } 29 | 30 | public CameraViewOverlay(Context context, AttributeSet attrs) { 31 | this(context, attrs, 0); 32 | } 33 | 34 | public CameraViewOverlay(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | setZOrderOnTop(true); 37 | holder = getHolder(); 38 | holder.setFormat(PixelFormat.TRANSPARENT); 39 | holder.addCallback(new SurfaceHolder.Callback() { 40 | @Override 41 | public void surfaceCreated(SurfaceHolder holder) { 42 | clear(); 43 | } 44 | 45 | @Override 46 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 47 | clear(); 48 | } 49 | 50 | @Override 51 | public void surfaceDestroyed(SurfaceHolder holder) { 52 | 53 | } 54 | }); 55 | } 56 | 57 | public void setCanvasDrawer(CanvasDrawer drawer) { 58 | canvasDrawer = drawer; 59 | paints = drawer.initPaints(); 60 | } 61 | 62 | void focusRequestAt(int x, int y) { 63 | if (x >= 0 && x <= getMeasuredWidth() && y >= 0 && y <= getMeasuredHeight()) { 64 | focusPoint = new Point(x, y); 65 | } 66 | 67 | drawIndicator(); 68 | } 69 | 70 | void focusFinished() { 71 | focusPoint = null; 72 | postDelayed(this::clear, 300); 73 | } 74 | 75 | private void drawIndicator() { 76 | invalidate(); 77 | if (holder.getSurface().isValid()) { 78 | final Canvas canvas = holder.lockCanvas(); 79 | if (canvas != null) { 80 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 81 | canvas.drawColor(Color.TRANSPARENT); 82 | if (canvasDrawer != null) { 83 | canvasDrawer.draw(canvas, focusPoint, paints); 84 | } 85 | holder.unlockCanvasAndPost(canvas); 86 | } 87 | } 88 | } 89 | 90 | private void clear() { 91 | Canvas canvas = holder.lockCanvas(); 92 | if(canvas != null) { 93 | canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); 94 | holder.unlockCanvasAndPost(canvas); 95 | } 96 | } 97 | 98 | private static final int SHUTTER_ONE_WAY_TIME = 150; 99 | 100 | public void shot() { 101 | int colorFrom = Color.TRANSPARENT; 102 | int colorTo = 0xaf000000; 103 | ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); 104 | colorAnimation.setInterpolator(new DecelerateInterpolator()); 105 | colorAnimation.setDuration(SHUTTER_ONE_WAY_TIME); 106 | colorAnimation.addUpdateListener(animator -> setBackgroundColor((int) animator.getAnimatedValue())); 107 | colorAnimation.addListener(new AnimatorListenerAdapter() { 108 | @Override 109 | public void onAnimationCancel(Animator animation) { 110 | setBackgroundColor(colorFrom); 111 | } 112 | }); 113 | colorAnimation.start(); 114 | postDelayed(colorAnimation::reverse, SHUTTER_ONE_WAY_TIME); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Camera [![gitHub release](https://img.shields.io/github/release/duanhong169/Camera.svg?style=social)](https://github.com/duanhong169/Camera/releases) [![platform](https://img.shields.io/badge/platform-android-brightgreen.svg)](https://developer.android.com/index.html) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Camera-green.svg?style=flat)](https://android-arsenal.com/details/1/7018) [![license](https://img.shields.io/badge/license-Apache%202-green.svg)](https://github.com/duanhong169/Camera/blob/master/LICENSE) [![Build status](https://build.appcenter.ms/v0.1/apps/09612c7d-00b4-4b16-86b5-054c45d749f8/branches/master/badge)](https://appcenter.ms) 2 | 3 | Use Android camera to take pictures and videos, based on [camera2](https://developer.android.com/reference/android/hardware/camera2/package-summary) api. 4 | 5 | 6 | 7 | ## Features 8 | 9 | * Auto filled `CameraView` for previewing 10 | * Support both image capture & video record 11 | * Configurable audio/video size and aspect ratio, auto focus, tap to focus, flash control, pinch to zoom, etc. 12 | 13 | ## Gradle 14 | 15 | ``` 16 | dependencies { 17 | implementation 'com.github.duanhong169:camera:${latestVersion}' 18 | ... 19 | } 20 | ``` 21 | 22 | > Replace `${latestVersion}` with the latest version code. See [releases](https://github.com/duanhong169/Camera/releases). 23 | 24 | ## Usage 25 | 26 | * Add `CameraView` into your layout xml: 27 | 28 | ```xml 29 | 42 | ``` 43 | 44 | > See [`top_defaults_camera_attrs.xml`](./camera/src/main/res/values/top_defaults_camera_attrs.xml) for all supported attributes. 45 | 46 | * Create a `Photographer` with the `CameraView`: 47 | 48 | ```java 49 | @Override 50 | protected void onCreate(@Nullable Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | // ... 54 | 55 | CameraView preview = findViewById(R.id.preview); 56 | photographer = PhotographerFactory.createPhotographerWithCamera2(this, preview); 57 | 58 | // ... 59 | } 60 | ``` 61 | 62 | * Implement and set `Photographer.OnEventListener` to receive events from the camera: 63 | 64 | ```java 65 | photographer.setOnEventListener(new SimpleOnEventListener() { 66 | @Override 67 | public void onDeviceConfigured() {} 68 | 69 | @Override 70 | public void onPreviewStarted() {} 71 | 72 | @Override 73 | public void onZoomChanged(float zoom) {} 74 | 75 | @Override 76 | public void onPreviewStopped() {} 77 | 78 | @Override 79 | public void onStartRecording() {} 80 | 81 | @Override 82 | public void onFinishRecording(String filePath) {} 83 | 84 | @Override 85 | public void onShotFinished(String filePath) {} 86 | 87 | @Override 88 | public void onError(Error error) {} 89 | }); 90 | ``` 91 | 92 | * Start/stop preview in `onResume()`/`onPause()`: 93 | 94 | ```java 95 | @Override 96 | protected void onResume() { 97 | super.onResume(); 98 | photographer.startPreview(); 99 | } 100 | 101 | @Override 102 | protected void onPause() { 103 | photographer.stopPreview(); 104 | super.onPause(); 105 | } 106 | ``` 107 | 108 | * `PhotographerHelper` is your friend: 109 | 110 | ```java 111 | photographerHelper = new PhotographerHelper(photographer); // init with photographer 112 | photographerHelper.setFileDir(Commons.MEDIA_DIR); // set directory for image/video saving 113 | photographerHelper.flip(); // flip back/front camera 114 | photographerHelper.switchMode(); // switch between image capture/video record 115 | ``` 116 | 117 | See a complete usage in the app sample code. 118 | 119 | ## Credits 120 | 121 | * [google/cameraview](https://github.com/google/cameraview) 122 | * [googlesamples/android-Camera2Basic](https://github.com/googlesamples/android-Camera2Basic) 123 | * [googlesamples/android-Camera2Video](https://github.com/googlesamples/android-Camera2Video) 124 | 125 | ## License 126 | 127 | See the [LICENSE](./LICENSE) file. -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_video_record.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 24 | 33 | 34 | 43 | 44 | 56 | 57 | 66 | 67 | 76 | 77 | 86 | 87 | 94 | 95 | 102 | 103 | 113 | 114 | 115 | 121 | 122 | 130 | 131 | 138 | 139 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/AspectRatio.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package top.defaults.camera; 18 | 19 | import android.os.Parcel; 20 | import android.os.Parcelable; 21 | import android.support.annotation.NonNull; 22 | import android.support.v4.util.SparseArrayCompat; 23 | 24 | /** 25 | * Immutable class for describing proportional relationship between width and height. 26 | */ 27 | public class AspectRatio implements Comparable, Parcelable { 28 | 29 | private final static SparseArrayCompat> sCache 30 | = new SparseArrayCompat<>(16); 31 | 32 | private final int mX; 33 | private final int mY; 34 | 35 | /** 36 | * Returns an instance of {@link AspectRatio} specified by {@code x} and {@code y} values. 37 | * The values {@code x} and {@code} will be reduced by their greatest common divider. 38 | * 39 | * @param x The width 40 | * @param y The height 41 | * @return An instance of {@link AspectRatio} 42 | */ 43 | public static AspectRatio of(int x, int y) { 44 | int gcd = gcd(x, y); 45 | x /= gcd; 46 | y /= gcd; 47 | SparseArrayCompat arrayX = sCache.get(x); 48 | if (arrayX == null) { 49 | AspectRatio ratio = new AspectRatio(x, y); 50 | arrayX = new SparseArrayCompat<>(); 51 | arrayX.put(y, ratio); 52 | sCache.put(x, arrayX); 53 | return ratio; 54 | } else { 55 | AspectRatio ratio = arrayX.get(y); 56 | if (ratio == null) { 57 | ratio = new AspectRatio(x, y); 58 | arrayX.put(y, ratio); 59 | } 60 | return ratio; 61 | } 62 | } 63 | 64 | public static AspectRatio of(Size size) { 65 | return of(size.getWidth(), size.getHeight()); 66 | } 67 | 68 | /** 69 | * Parse an {@link AspectRatio} from a {@link String} formatted like "4:3". 70 | * 71 | * @param s The string representation of the aspect ratio 72 | * @return The aspect ratio 73 | * @throws IllegalArgumentException when the format is incorrect. 74 | */ 75 | public static AspectRatio parse(String s) { 76 | int position = s.indexOf(':'); 77 | if (position == -1) { 78 | throw new IllegalArgumentException("Malformed aspect ratio: " + s); 79 | } 80 | try { 81 | int x = Integer.parseInt(s.substring(0, position)); 82 | int y = Integer.parseInt(s.substring(position + 1)); 83 | return AspectRatio.of(x, y); 84 | } catch (NumberFormatException e) { 85 | throw new IllegalArgumentException("Malformed aspect ratio: " + s, e); 86 | } 87 | } 88 | 89 | private AspectRatio(int x, int y) { 90 | mX = x; 91 | mY = y; 92 | } 93 | 94 | public int getX() { 95 | return mX; 96 | } 97 | 98 | public int getY() { 99 | return mY; 100 | } 101 | 102 | public boolean matches(Size size) { 103 | int gcd = gcd(size.getWidth(), size.getHeight()); 104 | int x = size.getWidth() / gcd; 105 | int y = size.getHeight() / gcd; 106 | return mX == x && mY == y; 107 | } 108 | 109 | @Override 110 | public boolean equals(Object o) { 111 | if (o == null) { 112 | return false; 113 | } 114 | if (this == o) { 115 | return true; 116 | } 117 | if (o instanceof AspectRatio) { 118 | AspectRatio ratio = (AspectRatio) o; 119 | return mX == ratio.mX && mY == ratio.mY; 120 | } 121 | return false; 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return mX + ":" + mY; 127 | } 128 | 129 | public float toFloat() { 130 | return (float) mX / mY; 131 | } 132 | 133 | @Override 134 | public int hashCode() { 135 | // assuming most sizes are <2^16, doing a rotate will give us perfect hashing 136 | return mY ^ ((mX << (Integer.SIZE / 2)) | (mX >>> (Integer.SIZE / 2))); 137 | } 138 | 139 | @Override 140 | public int compareTo(@NonNull AspectRatio another) { 141 | if (equals(another)) { 142 | return 0; 143 | } else if (toFloat() - another.toFloat() > 0) { 144 | return 1; 145 | } 146 | return -1; 147 | } 148 | 149 | /** 150 | * @return The inverse of this {@link AspectRatio}. 151 | */ 152 | public AspectRatio inverse() { 153 | //noinspection SuspiciousNameCombination 154 | return AspectRatio.of(mY, mX); 155 | } 156 | 157 | private static int gcd(int a, int b) { 158 | while (b != 0) { 159 | int c = b; 160 | b = a % b; 161 | a = c; 162 | } 163 | return a; 164 | } 165 | 166 | @Override 167 | public int describeContents() { 168 | return 0; 169 | } 170 | 171 | @Override 172 | public void writeToParcel(Parcel dest, int flags) { 173 | dest.writeInt(mX); 174 | dest.writeInt(mY); 175 | } 176 | 177 | public static final Parcelable.Creator CREATOR 178 | = new Parcelable.Creator() { 179 | 180 | @Override 181 | public AspectRatio createFromParcel(Parcel source) { 182 | int x = source.readInt(); 183 | int y = source.readInt(); 184 | return AspectRatio.of(x, y); 185 | } 186 | 187 | @Override 188 | public AspectRatio[] newArray(int size) { 189 | return new AspectRatio[size]; 190 | } 191 | }; 192 | 193 | } 194 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.content.pm.ResolveInfo; 9 | import android.net.Uri; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.support.design.widget.Snackbar; 13 | import android.support.v4.content.FileProvider; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.BaseAdapter; 18 | import android.widget.GridView; 19 | import android.widget.ImageView; 20 | import android.widget.TextView; 21 | import android.widget.Toast; 22 | 23 | import com.bumptech.glide.Glide; 24 | import com.jakewharton.rxbinding2.view.RxView; 25 | import com.tbruyelle.rxpermissions2.RxPermissions; 26 | 27 | import java.io.File; 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | import java.util.List; 31 | 32 | import butterknife.BindView; 33 | import butterknife.ButterKnife; 34 | import top.defaults.cameraapp.options.Commons; 35 | 36 | public class MainActivity extends AppCompatActivity { 37 | 38 | private View prepareToRecord; 39 | 40 | @BindView(R.id.media_dir) TextView mediaDir; 41 | @BindView(R.id.gallery) GridView gallery; 42 | private List mediaFiles = new ArrayList<>(); 43 | private MediaFileAdapter adapter; 44 | 45 | @SuppressLint("CheckResult") 46 | @Override 47 | protected void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setContentView(R.layout.activity_main); 50 | ButterKnife.bind(this); 51 | 52 | RxPermissions rxPermissions = new RxPermissions(this); 53 | prepareToRecord = findViewById(R.id.open_camera); 54 | RxView.clicks(prepareToRecord) 55 | .compose(rxPermissions.ensure(Manifest.permission.CAMERA, 56 | Manifest.permission.RECORD_AUDIO, 57 | Manifest.permission.WRITE_EXTERNAL_STORAGE)) 58 | .subscribe(granted -> { 59 | if (granted) { 60 | startVideoRecordActivity(); 61 | } else { 62 | Snackbar.make(prepareToRecord, getString(R.string.no_enough_permission), Snackbar.LENGTH_SHORT).setAction("Confirm", null).show(); 63 | } 64 | }); 65 | 66 | mediaDir.setText(String.format("Media files are saved under:\n%s", Commons.MEDIA_DIR)); 67 | 68 | adapter = new MediaFileAdapter(this, mediaFiles); 69 | gallery.setAdapter(adapter); 70 | gallery.setOnItemClickListener((parent, view, position, id) -> { 71 | File file = adapter.getItem(position); 72 | playOrViewMedia(file); 73 | }); 74 | } 75 | 76 | private void startVideoRecordActivity() { 77 | Intent intent = new Intent(this, PhotographerActivity.class); 78 | startActivity(intent); 79 | } 80 | 81 | @Override 82 | protected void onResume() { 83 | super.onResume(); 84 | File file = new File(Commons.MEDIA_DIR); 85 | if (file.isDirectory()) { 86 | mediaFiles.clear(); 87 | File[] files = file.listFiles(); 88 | Arrays.sort(files, (f1, f2) -> { 89 | if (f1.lastModified() - f2.lastModified() == 0) { 90 | return 0; 91 | } else { 92 | return f1.lastModified() - f2.lastModified() > 0 ? -1 : 1; 93 | } 94 | }); 95 | mediaFiles.addAll(Arrays.asList(files)); 96 | adapter.notifyDataSetChanged(); 97 | } 98 | } 99 | 100 | private void playOrViewMedia(File file) { 101 | Intent intent = new Intent(Intent.ACTION_VIEW); 102 | Uri uriForFile = FileProvider.getUriForFile(MainActivity.this, getApplicationContext().getPackageName() + ".provider", file); 103 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 104 | uriForFile = Uri.fromFile(file); 105 | } 106 | intent.setDataAndType(uriForFile, isVideo(file) ? "video/mp4" : "image/jpg"); 107 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 108 | 109 | PackageManager packageManager = getPackageManager(); 110 | List activities = packageManager.queryIntentActivities(intent, 0); 111 | boolean isIntentSafe = activities.size() > 0; 112 | 113 | if (isIntentSafe) { 114 | startActivity(intent); 115 | } else { 116 | Toast.makeText(MainActivity.this, "No media viewer found", Toast.LENGTH_SHORT).show(); 117 | } 118 | } 119 | 120 | private class MediaFileAdapter extends BaseAdapter { 121 | 122 | private List files; 123 | 124 | private Context context; 125 | 126 | MediaFileAdapter(Context c, List files) { 127 | context = c; 128 | this.files = files; 129 | } 130 | 131 | public int getCount() { 132 | return files.size(); 133 | } 134 | 135 | public File getItem(int position) { 136 | return files.get(position); 137 | } 138 | 139 | public long getItemId(int position) { 140 | return position; 141 | } 142 | 143 | public View getView(int position, View convertView, ViewGroup parent) { 144 | ImageView imageView; 145 | if (convertView == null) { 146 | convertView = getLayoutInflater().inflate(R.layout.item_media, parent, false); 147 | } 148 | imageView = convertView.findViewById(R.id.item_image); 149 | View indicator = convertView.findViewById(R.id.item_indicator); 150 | File file = getItem(position); 151 | if (isVideo(file)) { 152 | indicator.setVisibility(View.VISIBLE); 153 | } else { 154 | indicator.setVisibility(View.GONE); 155 | } 156 | Glide.with(context).load(file).into(imageView); 157 | return convertView; 158 | } 159 | } 160 | 161 | private boolean isVideo(File file) { 162 | return file != null && file.getName().endsWith(".mp4"); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 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 | 92 | 102 | 103 | 104 | 105 | 106 | 107 | 109 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/CameraView.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.view.ViewCompat; 9 | import android.util.AttributeSet; 10 | import android.view.GestureDetector; 11 | import android.view.MotionEvent; 12 | import android.view.ScaleGestureDetector; 13 | import android.view.ViewGroup; 14 | import android.widget.RelativeLayout; 15 | 16 | import java.util.LinkedList; 17 | import java.util.List; 18 | 19 | public class CameraView extends RelativeLayout { 20 | 21 | private Context context; 22 | private AutoFitTextureView textureView; 23 | private CameraViewOverlay overlay; 24 | private final DisplayOrientationDetector displayOrientationDetector; 25 | private String aspectRatio; 26 | private boolean autoFocus; 27 | private int facing; 28 | private int flash; 29 | private int mode; 30 | private boolean pinchToZoom; 31 | private GestureDetector gestureDetector; 32 | private ScaleGestureDetector scaleGestureDetector; 33 | 34 | public CameraView(@NonNull Context context) { 35 | this(context, null); 36 | } 37 | 38 | public CameraView(@NonNull Context context, @Nullable AttributeSet attrs) { 39 | this(context, attrs, 0); 40 | } 41 | 42 | @SuppressLint("ClickableViewAccessibility") 43 | public CameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 44 | super(context, attrs, defStyleAttr); 45 | 46 | this.context = context; 47 | textureView = new AutoFitTextureView(context); 48 | textureView.setId(R.id.textureView); 49 | LayoutParams textureViewParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 50 | textureViewParams.addRule(CENTER_IN_PARENT); 51 | addView(textureView, textureViewParams); 52 | 53 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CameraView); 54 | aspectRatio = typedArray.getString(R.styleable.CameraView_aspectRatio); 55 | autoFocus = typedArray.getBoolean(R.styleable.CameraView_autoFocus, true); 56 | facing = typedArray.getInt(R.styleable.CameraView_facing, Values.FACING_BACK); 57 | flash = typedArray.getInt(R.styleable.CameraView_flash, Values.FLASH_OFF); 58 | mode = typedArray.getInt(R.styleable.CameraView_mode, Values.MODE_IMAGE); 59 | boolean fillSpace = typedArray.getBoolean(R.styleable.CameraView_fillSpace, false); 60 | textureView.setFillSpace(fillSpace); 61 | pinchToZoom = typedArray.getBoolean(R.styleable.CameraView_pinchToZoom, true); 62 | boolean showFocusIndicator = typedArray.getBoolean(R.styleable.CameraView_showFocusIndicator, true); 63 | typedArray.recycle(); 64 | 65 | addOverlay(); 66 | 67 | if (showFocusIndicator) { 68 | setFocusIndicatorDrawer(new CanvasDrawer.DefaultCanvasDrawer()); 69 | } 70 | 71 | displayOrientationDetector = new DisplayOrientationDetector(context) { 72 | @Override 73 | public void onDisplayOrientationChanged(int displayOrientation) { 74 | textureView.setDisplayOrientation(displayOrientation); 75 | } 76 | }; 77 | 78 | gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 79 | @Override 80 | public boolean onSingleTapConfirmed(MotionEvent e) { 81 | dispatchOnSingleTap(e); 82 | return true; 83 | } 84 | }); 85 | scaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { 86 | @Override 87 | public boolean onScale(ScaleGestureDetector detector) { 88 | if (pinchToZoom) { 89 | dispatchOnScale(detector.getScaleFactor()); 90 | } 91 | return true; 92 | } 93 | }); 94 | 95 | textureView.setOnTouchListener((v, event) -> { 96 | if (gestureDetector.onTouchEvent(event)) { 97 | return true; 98 | } 99 | if (scaleGestureDetector.onTouchEvent(event)) { 100 | return true; 101 | } 102 | return true; 103 | }); 104 | } 105 | 106 | void assign(InternalPhotographer photographer) { 107 | photographer.setMode(mode); 108 | photographer.setAspectRatio(AspectRatio.parse(aspectRatio)); 109 | photographer.setAutoFocus(autoFocus); 110 | photographer.setFacing(facing); 111 | photographer.setFlash(flash); 112 | } 113 | 114 | @Override 115 | protected void onAttachedToWindow() { 116 | super.onAttachedToWindow(); 117 | if (!isInEditMode()) { 118 | displayOrientationDetector.enable(ViewCompat.getDisplay(this)); 119 | } 120 | } 121 | 122 | @Override 123 | protected void onDetachedFromWindow() { 124 | if (!isInEditMode()) { 125 | displayOrientationDetector.disable(); 126 | } 127 | super.onDetachedFromWindow(); 128 | } 129 | 130 | AutoFitTextureView getTextureView() { 131 | return textureView; 132 | } 133 | 134 | public boolean isFillSpace() { 135 | return textureView.isFillSpace(); 136 | } 137 | 138 | public void setFillSpace(boolean fillSpace) { 139 | textureView.setFillSpace(fillSpace); 140 | } 141 | 142 | public void setPinchToZoom(boolean pinchToZoom) { 143 | this.pinchToZoom = pinchToZoom; 144 | } 145 | 146 | private void addOverlay() { 147 | overlay = new CameraViewOverlay(context); 148 | LayoutParams overlayParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 149 | overlayParams.addRule(ALIGN_LEFT, R.id.textureView); 150 | overlayParams.addRule(ALIGN_TOP, R.id.textureView); 151 | overlayParams.addRule(ALIGN_RIGHT, R.id.textureView); 152 | overlayParams.addRule(ALIGN_BOTTOM, R.id.textureView); 153 | addView(overlay, overlayParams); 154 | } 155 | 156 | public void setFocusIndicatorDrawer(CanvasDrawer drawer) { 157 | overlay.setCanvasDrawer(drawer); 158 | } 159 | 160 | void focusRequestAt(int x, int y) { 161 | overlay.focusRequestAt(x, y); 162 | } 163 | 164 | void focusFinished() { 165 | overlay.focusFinished(); 166 | } 167 | 168 | void shot() { 169 | overlay.shot(); 170 | } 171 | 172 | public interface Callback extends AutoFitTextureView.Callback { 173 | void onSingleTap(MotionEvent e); 174 | 175 | void onScale(float scaleFactor); 176 | } 177 | 178 | private List callbacks = new LinkedList<>(); 179 | 180 | public void addCallback(Callback callback) { 181 | if (callback != null) { 182 | callbacks.add(callback); 183 | textureView.addCallback(callback); 184 | } 185 | } 186 | 187 | private void dispatchOnSingleTap(MotionEvent e) { 188 | for (Callback callback : callbacks) { 189 | callback.onSingleTap(e); 190 | } 191 | } 192 | 193 | private void dispatchOnScale(float scaleFactor) { 194 | for (Callback callback : callbacks) { 195 | callback.onScale(scaleFactor); 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/AutoFitTextureView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package top.defaults.camera; 18 | 19 | import android.content.Context; 20 | import android.graphics.Matrix; 21 | import android.graphics.SurfaceTexture; 22 | import android.util.AttributeSet; 23 | import android.view.Surface; 24 | import android.view.TextureView; 25 | 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | 29 | /** 30 | * A {@link TextureView} that can be adjusted to a specified aspect ratio. 31 | */ 32 | class AutoFitTextureView extends TextureView { 33 | 34 | private int ratioWidth = 0; 35 | private int ratioHeight = 0; 36 | private boolean fillSpace = false; 37 | private int displayOrientation; 38 | 39 | public AutoFitTextureView(Context context) { 40 | this(context, null); 41 | } 42 | 43 | public AutoFitTextureView(Context context, AttributeSet attrs) { 44 | this(context, attrs, 0); 45 | } 46 | 47 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) { 48 | super(context, attrs, defStyle); 49 | setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { 50 | 51 | @Override 52 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 53 | configureTransform(); 54 | dispatchSurfaceChanged(); 55 | } 56 | 57 | @Override 58 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 59 | configureTransform(); 60 | dispatchSurfaceChanged(); 61 | } 62 | 63 | @Override 64 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 65 | return true; 66 | } 67 | 68 | @Override 69 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 70 | } 71 | }); 72 | } 73 | 74 | /** 75 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio 76 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that 77 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result. 78 | * 79 | * @param width Relative horizontal size 80 | * @param height Relative vertical size 81 | */ 82 | public void setAspectRatio(int width, int height) { 83 | if (width < 0 || height < 0) { 84 | throw new IllegalArgumentException("Size cannot be negative."); 85 | } 86 | ratioWidth = width; 87 | ratioHeight = height; 88 | requestLayout(); 89 | } 90 | 91 | public boolean isFillSpace() { 92 | return fillSpace; 93 | } 94 | 95 | public void setFillSpace(boolean fillSpace) { 96 | this.fillSpace = fillSpace; 97 | requestLayout(); 98 | } 99 | 100 | @Override 101 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 102 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 103 | int width = MeasureSpec.getSize(widthMeasureSpec); 104 | int height = MeasureSpec.getSize(heightMeasureSpec); 105 | if (0 == ratioWidth || 0 == ratioHeight) { 106 | setMeasuredDimension(width, height); 107 | } else { 108 | // is filling space by default 109 | boolean isFillSpaceWithoutScale = width == height * ratioWidth / ratioHeight; 110 | if (isFillSpaceWithoutScale) { 111 | setMeasuredDimension(width, height); 112 | return; 113 | } 114 | 115 | if (fillSpace) { 116 | if (width < height * ratioWidth / ratioHeight) { 117 | setMeasuredDimension(height * ratioWidth / ratioHeight, height); 118 | } else { 119 | setMeasuredDimension(width, width * ratioHeight / ratioWidth); 120 | } 121 | } else { 122 | if (width < height * ratioWidth / ratioHeight) { 123 | setMeasuredDimension(width, width * ratioHeight / ratioWidth); 124 | } else { 125 | setMeasuredDimension(height * ratioWidth / ratioHeight, height); 126 | } 127 | } 128 | } 129 | } 130 | 131 | Surface getSurface() { 132 | return new Surface(getSurfaceTexture()); 133 | } 134 | 135 | void setBufferSize(int width, int height) { 136 | getSurfaceTexture().setDefaultBufferSize(width, height); 137 | } 138 | 139 | int getDisplayOrientation() { 140 | return displayOrientation; 141 | } 142 | 143 | void setDisplayOrientation(int displayOrientation) { 144 | this.displayOrientation = displayOrientation; 145 | configureTransform(); 146 | } 147 | 148 | private void configureTransform() { 149 | Matrix matrix = new Matrix(); 150 | if (displayOrientation % 180 == 90) { 151 | final int width = getWidth(); 152 | final int height = getHeight(); 153 | // Rotate the camera preview when the screen is landscape. 154 | matrix.setPolyToPoly( 155 | new float[]{ 156 | 0.f, 0.f, // top left 157 | width, 0.f, // top right 158 | 0.f, height, // bottom left 159 | width, height, // bottom right 160 | }, 0, 161 | displayOrientation == 90 ? 162 | // Clockwise 163 | new float[]{ 164 | 0.f, height, // top left 165 | 0.f, 0.f, // top right 166 | width, height, // bottom left 167 | width, 0.f, // bottom right 168 | } : // mDisplayOrientation == 270 169 | // Counter-clockwise 170 | new float[]{ 171 | width, 0.f, // top left 172 | width, height, // top right 173 | 0.f, 0.f, // bottom left 174 | 0.f, height, // bottom right 175 | }, 0, 176 | 4); 177 | } else if (displayOrientation == 180) { 178 | matrix.postRotate(180, getWidth() / 2, getHeight() / 2); 179 | } 180 | setTransform(matrix); 181 | } 182 | 183 | interface Callback { 184 | void onSurfaceChanged(); 185 | } 186 | 187 | private List callbacks = new LinkedList<>(); 188 | 189 | public void addCallback(Callback callback) { 190 | if (callback != null) { 191 | callbacks.add(callback); 192 | } 193 | } 194 | 195 | private void dispatchSurfaceChanged() { 196 | for (Callback callback : callbacks) { 197 | callback.onSurfaceChanged(); 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /camera/src/main/java/top/defaults/camera/Utils.java: -------------------------------------------------------------------------------- 1 | package top.defaults.camera; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.graphics.Rect; 7 | import android.net.Uri; 8 | import android.os.Environment; 9 | import android.util.SparseIntArray; 10 | import android.view.MotionEvent; 11 | import android.view.Surface; 12 | import android.view.TextureView; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.Locale; 17 | import java.util.Map; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | 21 | @SuppressWarnings("unused") 22 | public class Utils { 23 | 24 | private static Pattern pattern = Pattern.compile("^#(\\d+), (.+)"); 25 | 26 | static String exceptionMessage(int code, String message) { 27 | return String.format(Locale.getDefault(), "#%d, %s", code, message); 28 | } 29 | 30 | private static int codeFromThrowable(Throwable throwable, int fallback) { 31 | int errorCode = fallback; 32 | 33 | String message = throwable.getMessage(); 34 | if (message != null) { 35 | Matcher matcher = pattern.matcher(message); 36 | if (matcher.find()) { 37 | errorCode = Integer.parseInt(matcher.group(1)); 38 | } 39 | } 40 | 41 | return errorCode; 42 | } 43 | 44 | private static String messageFromThrowable(Throwable throwable) { 45 | String message = throwable.getMessage(); 46 | 47 | if (message == null) { 48 | message = throwable.toString(); 49 | } else { 50 | Matcher matcher = pattern.matcher(message); 51 | if (matcher.find()) { 52 | message = matcher.group(2); 53 | } 54 | } 55 | return message; 56 | } 57 | 58 | static Error errorFromThrowable(Throwable throwable) { 59 | return errorFromThrowable(throwable, Error.ERROR_DEFAULT_CODE); 60 | } 61 | 62 | private static Error errorFromThrowable(Throwable throwable, int fallback) { 63 | return new Error(codeFromThrowable(throwable, fallback), messageFromThrowable(throwable)); 64 | } 65 | 66 | static boolean napInterrupted() { 67 | return !sleep(1); 68 | } 69 | 70 | private static boolean sleep(long millis) { 71 | try { 72 | Thread.sleep(millis); 73 | } catch (Exception e) { 74 | e.printStackTrace(); 75 | return false; 76 | } 77 | return true; 78 | } 79 | 80 | static boolean getBoolean(Map params, String key, boolean defaultValue) { 81 | boolean value = defaultValue; 82 | if (params == null) { 83 | return value; 84 | } 85 | 86 | Object valueObject = params.get(key); 87 | if (valueObject instanceof Boolean) { 88 | value = (Boolean) valueObject; 89 | } 90 | return value; 91 | } 92 | 93 | static int getInt(Map params, String key, int defaultValue) { 94 | int value = defaultValue; 95 | if (params == null) { 96 | return value; 97 | } 98 | 99 | Object valueObject = params.get(key); 100 | if (valueObject instanceof Integer) { 101 | value = (Integer) valueObject; 102 | } 103 | return value; 104 | } 105 | 106 | static String getString(Map params, String key, String defaultValue) { 107 | String value = defaultValue; 108 | if (params == null) { 109 | return value; 110 | } 111 | 112 | Object valueObject = params.get(key); 113 | if (valueObject instanceof String) { 114 | value = (String) valueObject; 115 | if (value.length() == 0) { 116 | value = defaultValue; 117 | } 118 | } 119 | return value; 120 | } 121 | 122 | public static void addMediaToGallery(Context context, String photoPath) { 123 | Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 124 | File photoFile = new File(photoPath); 125 | Uri contentUri = Uri.fromFile(photoFile); 126 | mediaScanIntent.setData(contentUri); 127 | context.sendBroadcast(mediaScanIntent); 128 | } 129 | 130 | static String getImageFilePath() throws IOException { 131 | return getFilePath(".jpg"); 132 | } 133 | 134 | static String getVideoFilePath() throws IOException { 135 | return getFilePath(".mp4"); 136 | } 137 | 138 | private static String fileDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/TopDefaultsCamera/"; 139 | 140 | static void setFileDir(String fileDir) { 141 | Utils.fileDir = fileDir; 142 | } 143 | 144 | private static String getFilePath(String fileSuffix) throws IOException { 145 | final File dir = new File(fileDir); 146 | if (!dir.exists()) { 147 | boolean result = dir.mkdirs(); 148 | if (!result) { 149 | throw new IOException(Utils.exceptionMessage(Error.ERROR_STORAGE, "Unable to create folder")); 150 | } 151 | } 152 | return dir.getAbsolutePath() + "/" + System.currentTimeMillis() + fileSuffix; 153 | } 154 | 155 | static boolean checkFloatEqual(float a, float b) { 156 | return Math.abs(a - b) < 0.001; 157 | } 158 | 159 | private static final int FOCUS_AREA_SIZE = 150; 160 | 161 | static Rect calculateFocusArea(Rect sensorArraySize, int displayOrientation, TextureView textureView, MotionEvent event) { 162 | final int eventX = (int) event.getX(); 163 | final int eventY = (int) event.getY(); 164 | 165 | final int focusX; 166 | final int focusY; 167 | 168 | switch (displayOrientation) { 169 | case 0: 170 | focusX = (int)((eventX / (float)textureView.getWidth()) * (float)sensorArraySize.width()); 171 | focusY = (int)((eventY / (float)textureView.getHeight()) * (float)sensorArraySize.height()); 172 | break; 173 | case 180: 174 | focusX = (int)((1 - (eventX / (float)textureView.getWidth())) * (float)sensorArraySize.width()); 175 | focusY = (int)((1 - (eventY / (float)textureView.getHeight())) * (float)sensorArraySize.height()); 176 | break; 177 | case 270: 178 | focusX = (int)((1- (eventY / (float)textureView.getHeight())) * (float)sensorArraySize.width()); 179 | focusY = (int)((eventX / (float)textureView.getWidth()) * (float)sensorArraySize.height()); 180 | break; 181 | case 90: 182 | default: 183 | focusX = (int)((eventY / (float)textureView.getHeight()) * (float)sensorArraySize.width()); 184 | focusY = (int)((1 - (eventX / (float)textureView.getWidth())) * (float)sensorArraySize.height()); 185 | break; 186 | } 187 | 188 | int left = Math.max(focusX - FOCUS_AREA_SIZE, 0); 189 | int top = Math.max(focusY - FOCUS_AREA_SIZE, 0); 190 | int right = Math.min(left + FOCUS_AREA_SIZE * 2, sensorArraySize.width()); 191 | int bottom = Math.min(top + FOCUS_AREA_SIZE * 2, sensorArraySize.width()); 192 | return new Rect(left, top, right, bottom); 193 | } 194 | 195 | static int getDisplayOrientation(Activity activity, int sensorOrientation) { 196 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 197 | return getOrientation(sensorOrientation, rotation); 198 | } 199 | 200 | private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; 201 | private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; 202 | private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); 203 | private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); 204 | 205 | static { 206 | DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90); 207 | DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0); 208 | DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270); 209 | DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180); 210 | } 211 | 212 | static { 213 | INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270); 214 | INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180); 215 | INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90); 216 | INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); 217 | } 218 | 219 | static int getOrientation(int sensorOrientation, int displayRotation) { 220 | int degree = DEFAULT_ORIENTATIONS.get(displayRotation); 221 | switch (sensorOrientation) { 222 | case SENSOR_ORIENTATION_DEFAULT_DEGREES: 223 | degree = DEFAULT_ORIENTATIONS.get(displayRotation); 224 | break; 225 | case SENSOR_ORIENTATION_INVERSE_DEGREES: 226 | degree = INVERSE_ORIENTATIONS.get(displayRotation); 227 | break; 228 | } 229 | return degree; 230 | } 231 | 232 | static float clamp(float value, float min, float max) { 233 | if (value < min) { 234 | return min; 235 | } else if (value > max) { 236 | return max; 237 | } else { 238 | return value; 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/src/main/java/top/defaults/cameraapp/PhotographerActivity.java: -------------------------------------------------------------------------------- 1 | package top.defaults.cameraapp; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.Point; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.view.View; 11 | import android.widget.ImageButton; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import java.util.List; 16 | import java.util.Locale; 17 | import java.util.Set; 18 | 19 | import butterknife.BindView; 20 | import butterknife.ButterKnife; 21 | import butterknife.OnCheckedChanged; 22 | import butterknife.OnClick; 23 | import timber.log.Timber; 24 | import top.defaults.camera.CameraView; 25 | import top.defaults.camera.CanvasDrawer; 26 | import top.defaults.camera.Error; 27 | import top.defaults.camera.Photographer; 28 | import top.defaults.camera.PhotographerFactory; 29 | import top.defaults.camera.PhotographerHelper; 30 | import top.defaults.camera.SimpleOnEventListener; 31 | import top.defaults.camera.Size; 32 | import top.defaults.camera.Utils; 33 | import top.defaults.camera.Values; 34 | import top.defaults.cameraapp.dialog.PickerDialog; 35 | import top.defaults.cameraapp.dialog.SimplePickerDialog; 36 | import top.defaults.cameraapp.options.AspectRatioItem; 37 | import top.defaults.cameraapp.options.Commons; 38 | import top.defaults.cameraapp.options.SizeItem; 39 | import top.defaults.view.TextButton; 40 | 41 | public class PhotographerActivity extends AppCompatActivity { 42 | 43 | Photographer photographer; 44 | PhotographerHelper photographerHelper; 45 | private boolean isRecordingVideo; 46 | 47 | @BindView(R.id.preview) CameraView preview; 48 | @BindView(R.id.status) TextView statusTextView; 49 | 50 | @BindView(R.id.chooseSize) TextButton chooseSizeButton; 51 | @BindView(R.id.flash) TextButton flashTextButton; 52 | @BindView(R.id.flash_torch) ImageButton flashTorch; 53 | 54 | @BindView(R.id.switch_mode) TextButton switchButton; 55 | @BindView(R.id.action) ImageButton actionButton; 56 | @BindView(R.id.flip) ImageButton flipButton; 57 | 58 | @BindView(R.id.zoomValue) TextView zoomValueTextView; 59 | 60 | private int currentFlash = Values.FLASH_AUTO; 61 | 62 | private static final int[] FLASH_OPTIONS = { 63 | Values.FLASH_AUTO, 64 | Values.FLASH_OFF, 65 | Values.FLASH_ON, 66 | }; 67 | 68 | private static final int[] FLASH_ICONS = { 69 | R.drawable.ic_flash_auto, 70 | R.drawable.ic_flash_off, 71 | R.drawable.ic_flash_on, 72 | }; 73 | 74 | private static final int[] FLASH_TITLES = { 75 | R.string.flash_auto, 76 | R.string.flash_off, 77 | R.string.flash_on, 78 | }; 79 | 80 | @OnClick(R.id.chooseRatio) 81 | void chooseRatio() { 82 | List supportedAspectRatios = Commons.wrapItems(photographer.getSupportedAspectRatios(), AspectRatioItem::new); 83 | if (supportedAspectRatios != null) { 84 | SimplePickerDialog dialog = SimplePickerDialog.create(new PickerDialog.ActionListener() { 85 | @Override 86 | public void onCancelClick(PickerDialog dialog) { } 87 | 88 | @Override 89 | public void onDoneClick(PickerDialog dialog) { 90 | AspectRatioItem item = dialog.getSelectedItem(AspectRatioItem.class); 91 | photographer.setAspectRatio(item.get()); 92 | } 93 | }); 94 | dialog.setItems(supportedAspectRatios); 95 | dialog.setInitialItem(Commons.findEqual(supportedAspectRatios, photographer.getAspectRatio())); 96 | dialog.show(getFragmentManager(), "aspectRatio"); 97 | } 98 | } 99 | 100 | @OnClick(R.id.chooseSize) 101 | void chooseSize() { 102 | Size selectedSize = null; 103 | List supportedSizes = null; 104 | int mode = photographer.getMode(); 105 | if (mode == Values.MODE_VIDEO) { 106 | Set videoSizes = photographer.getSupportedVideoSizes(); 107 | selectedSize = photographer.getVideoSize(); 108 | if (videoSizes != null && videoSizes.size() > 0) { 109 | supportedSizes = Commons.wrapItems(videoSizes, SizeItem::new); 110 | } 111 | } else if (mode == Values.MODE_IMAGE) { 112 | Set imageSizes = photographer.getSupportedImageSizes(); 113 | selectedSize = photographer.getImageSize(); 114 | if (imageSizes != null && imageSizes.size() > 0) { 115 | supportedSizes = Commons.wrapItems(imageSizes, SizeItem::new); 116 | } 117 | } 118 | 119 | if (supportedSizes != null) { 120 | SimplePickerDialog dialog = SimplePickerDialog.create(new PickerDialog.ActionListener() { 121 | @Override 122 | public void onCancelClick(PickerDialog dialog) { } 123 | 124 | @Override 125 | public void onDoneClick(PickerDialog dialog) { 126 | SizeItem sizeItem = dialog.getSelectedItem(SizeItem.class); 127 | if (mode == Values.MODE_VIDEO) { 128 | photographer.setVideoSize(sizeItem.get()); 129 | } else { 130 | photographer.setImageSize(sizeItem.get()); 131 | } 132 | } 133 | }); 134 | dialog.setItems(supportedSizes); 135 | dialog.setInitialItem(Commons.findEqual(supportedSizes, selectedSize)); 136 | dialog.show(getFragmentManager(), "cameraOutputSize"); 137 | } 138 | } 139 | 140 | @OnCheckedChanged(R.id.fillSpace) 141 | void onFillSpaceChecked(boolean checked) { 142 | preview.setFillSpace(checked); 143 | } 144 | 145 | @OnCheckedChanged(R.id.enableZoom) 146 | void onEnableZoomChecked(boolean checked) { 147 | preview.setPinchToZoom(checked); 148 | } 149 | 150 | @OnClick(R.id.flash) 151 | void flash() { 152 | currentFlash = (currentFlash + 1) % FLASH_OPTIONS.length; 153 | flashTextButton.setText(FLASH_TITLES[currentFlash]); 154 | flashTextButton.setCompoundDrawablesWithIntrinsicBounds(FLASH_ICONS[currentFlash], 0, 0, 0); 155 | photographer.setFlash(FLASH_OPTIONS[currentFlash]); 156 | } 157 | 158 | @OnClick(R.id.action) 159 | void action() { 160 | int mode = photographer.getMode(); 161 | if (mode == Values.MODE_VIDEO) { 162 | if (isRecordingVideo) { 163 | finishRecordingIfNeeded(); 164 | } else { 165 | isRecordingVideo = true; 166 | photographer.startRecording(null); 167 | actionButton.setEnabled(false); 168 | } 169 | } else if (mode == Values.MODE_IMAGE) { 170 | photographer.takePicture(); 171 | } 172 | } 173 | 174 | @OnClick(R.id.flash_torch) 175 | void toggleFlashTorch() { 176 | int flash = photographer.getFlash(); 177 | if (flash == Values.FLASH_TORCH) { 178 | photographer.setFlash(currentFlash); 179 | flashTextButton.setEnabled(true); 180 | flashTorch.setImageResource(R.drawable.light_off); 181 | } else { 182 | photographer.setFlash(Values.FLASH_TORCH); 183 | flashTextButton.setEnabled(false); 184 | flashTorch.setImageResource(R.drawable.light_on); 185 | } 186 | } 187 | 188 | @OnClick(R.id.switch_mode) 189 | void switchMode() { 190 | photographerHelper.switchMode(); 191 | } 192 | 193 | @OnClick(R.id.flip) 194 | void flip() { 195 | photographerHelper.flip(); 196 | } 197 | 198 | @Override 199 | protected void onCreate(@Nullable Bundle savedInstanceState) { 200 | super.onCreate(savedInstanceState); 201 | setContentView(R.layout.activity_video_record); 202 | ButterKnife.bind(this); 203 | 204 | preview.setFocusIndicatorDrawer(new CanvasDrawer() { 205 | private static final int SIZE = 300; 206 | private static final int LINE_LENGTH = 50; 207 | 208 | @Override 209 | public Paint[] initPaints() { 210 | Paint focusPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 211 | focusPaint.setStyle(Paint.Style.STROKE); 212 | focusPaint.setStrokeWidth(2); 213 | focusPaint.setColor(Color.WHITE); 214 | return new Paint[]{ focusPaint }; 215 | } 216 | 217 | @Override 218 | public void draw(Canvas canvas, Point point, Paint[] paints) { 219 | if (paints == null || paints.length == 0) return; 220 | 221 | int left = point.x - (SIZE / 2); 222 | int top = point.y - (SIZE / 2); 223 | int right = point.x + (SIZE / 2); 224 | int bottom = point.y + (SIZE / 2); 225 | 226 | Paint paint = paints[0]; 227 | 228 | canvas.drawLine(left, top + LINE_LENGTH, left, top, paint); 229 | canvas.drawLine(left, top, left + LINE_LENGTH, top, paint); 230 | 231 | canvas.drawLine(right - LINE_LENGTH, top, right, top, paint); 232 | canvas.drawLine(right, top, right, top + LINE_LENGTH, paint); 233 | 234 | canvas.drawLine(right, bottom - LINE_LENGTH, right, bottom, paint); 235 | canvas.drawLine(right, bottom, right - LINE_LENGTH, bottom, paint); 236 | 237 | canvas.drawLine(left + LINE_LENGTH, bottom, left, bottom, paint); 238 | canvas.drawLine(left, bottom, left, bottom - LINE_LENGTH, paint); 239 | } 240 | }); 241 | photographer = PhotographerFactory.createPhotographerWithCamera2(this, preview); 242 | photographerHelper = new PhotographerHelper(photographer); 243 | photographerHelper.setFileDir(Commons.MEDIA_DIR); 244 | photographer.setOnEventListener(new SimpleOnEventListener() { 245 | @Override 246 | public void onDeviceConfigured() { 247 | if (photographer.getMode() == Values.MODE_VIDEO) { 248 | actionButton.setImageResource(R.drawable.record); 249 | chooseSizeButton.setText(R.string.video_size); 250 | switchButton.setText(R.string.video_mode); 251 | } else { 252 | actionButton.setImageResource(R.drawable.ic_camera); 253 | chooseSizeButton.setText(R.string.image_size); 254 | switchButton.setText(R.string.image_mode); 255 | } 256 | } 257 | 258 | @Override 259 | public void onZoomChanged(float zoom) { 260 | zoomValueTextView.setText(String.format(Locale.getDefault(), "%.1fX", zoom)); 261 | } 262 | 263 | @Override 264 | public void onStartRecording() { 265 | switchButton.setVisibility(View.INVISIBLE); 266 | flipButton.setVisibility(View.INVISIBLE); 267 | actionButton.setEnabled(true); 268 | actionButton.setImageResource(R.drawable.stop); 269 | statusTextView.setVisibility(View.VISIBLE); 270 | } 271 | 272 | @Override 273 | public void onFinishRecording(String filePath) { 274 | announcingNewFile(filePath); 275 | } 276 | 277 | @Override 278 | public void onShotFinished(String filePath) { 279 | announcingNewFile(filePath); 280 | } 281 | 282 | @Override 283 | public void onError(Error error) { 284 | Timber.e("Error happens: %s", error.getMessage()); 285 | } 286 | }); 287 | } 288 | 289 | @Override 290 | protected void onResume() { 291 | super.onResume(); 292 | enterFullscreen(); 293 | photographer.startPreview(); 294 | } 295 | 296 | @Override 297 | protected void onPause() { 298 | finishRecordingIfNeeded(); 299 | photographer.stopPreview(); 300 | super.onPause(); 301 | } 302 | 303 | private void enterFullscreen() { 304 | View decorView = getWindow().getDecorView(); 305 | decorView.setBackgroundColor(Color.BLACK); 306 | int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE 307 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 308 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 309 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 310 | | View.SYSTEM_UI_FLAG_FULLSCREEN 311 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 312 | decorView.setSystemUiVisibility(uiOptions); 313 | } 314 | 315 | private void finishRecordingIfNeeded() { 316 | if (isRecordingVideo) { 317 | isRecordingVideo = false; 318 | photographer.finishRecording(); 319 | statusTextView.setVisibility(View.INVISIBLE); 320 | switchButton.setVisibility(View.VISIBLE); 321 | flipButton.setVisibility(View.VISIBLE); 322 | actionButton.setEnabled(true); 323 | actionButton.setImageResource(R.drawable.record); 324 | } 325 | } 326 | 327 | private void announcingNewFile(String filePath) { 328 | Toast.makeText(PhotographerActivity.this, "File: " + filePath, Toast.LENGTH_SHORT).show(); 329 | Utils.addMediaToGallery(PhotographerActivity.this, filePath); 330 | } 331 | } 332 | --------------------------------------------------------------------------------