diff --git a/.gitignore b/.gitignore
index f6b286c..8ef66a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,33 @@ captures/
# Keystore files
*.jks
+
+# Built application files
+*.apk
+*.ap_
+
+# Files for the Dalvik VM
+*.dex
+
+# Java class files
+*.class
+
+# Generated files
+bin/
+gen/
+
+# Gradle files
+.gradle/
+build/
+/*/build/
+
+# Local configuration file (sdk path, etc)
+local.properties
+
+# Proguard folder generated by Eclipse
+proguard/
+
+# Log Files
+*.log
+
+.idea/*
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..b940ba7
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Florent37
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 447f65f..db59d50 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,118 @@
-# CameraFragment
\ No newline at end of file
+# CameraFragment
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/cameraFragment.gif)](https://github.com/florent37/CameraFragment)
+
+```java
+//you can configure the fragment by the configuration builder
+CameraFragment cameraFragment = CameraFragment.newInstance(new Configuration.Builder().build());
+
+getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, cameraFragment, FRAGMENT_TAG)
+ .commit();
+```
+
+## Actions
+
+You can directly take a photo / video with
+```java
+cameraFragment.takePhotoOrCaptureVideo();
+```
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/take_photo.gif)](https://github.com/florent37/CameraFragment)
+
+Flash can be enable / disabled ( `AUTO` / `OFF` / `ON` ) with
+
+```java
+cameraFragment.toggleFlashMode();
+```
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_flash.gif)](https://github.com/florent37/CameraFragment)
+
+Camera Type can be modified ( `BACK` / `FRONT` ) with
+
+```java
+cameraFragment.switchCameraType();
+```
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_camera.gif)](https://github.com/florent37/CameraFragment)
+
+Camera action ( `PHOTO` / `VIDEO` ) can be modified with
+
+```java
+cameraFragment.switchAction();
+```
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/switch_action.gif)](https://github.com/florent37/CameraFragment)
+
+And you can change the captured photo / video size with
+
+```java
+cameraFragment.openSettingDialog();
+```
+
+[![gif](https://raw.githubusercontent.com/florent37/CameraFragment/master/media/settings.gif)](https://github.com/florent37/CameraFragment)
+
+# Listeners
+
+## Result
+
+Get back the result of the camera record / photo in the `CameraFragmentResultListener`
+
+```java
+cameraFragment.setResultListener(new CameraFragmentResultListener() {
+ @Override
+ public void onVideoRecorded(byte[] bytes, String filePath) {
+ //called when the video record is finished and saved
+
+ startActivityForResult(PreviewActivity.newIntentVideo(MainActivity.this, filePath));
+ }
+
+ @Override
+ public void onPhotoTaken(byte[] bytes, String filePath) {
+ //called when the photo is taken and saved
+
+ startActivity(PreviewActivity.newIntentPhoto(MainActivity.this, filePath));
+ }
+});
+```
+
+## Camera Listener
+
+```java
+cameraFragment.setStateListener(new CameraFragmentStateListener() {
+
+ //when the current displayed camera is the back
+ void onCurrentCameraBack();
+ //when the current displayed camera is the front
+ void onCurrentCameraFront();
+
+ //when the flash is at mode auto
+ void onFlashAuto();
+ //when the flash is at on
+ void onFlashOn();
+ //when the flash is off
+ void onFlashOff();
+
+ //if the camera is ready to take a photo
+ void onCameraSetupForPhoto();
+ //if the camera is ready to take a video
+ void onCameraSetupForVideo();
+
+ //when the camera state is "ready to record a video"
+ void onRecordStateVideoReadyForRecord();
+ //when the camera state is "recording a video"
+ void onRecordStateVideoInProgress();
+ //when the camera state is "ready to take a photo"
+ void onRecordStatePhoto();
+
+ //after the rotation of the screen / camera
+ void shouldRotateControls(int degrees);
+
+ void onStartVideoRecord(File outputFile);
+ void onStopVideoRecord();
+});
+```
+
+## Text
+
+CameraFragment can ping you with the current record duration with `CameraFragmentTextListener`
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..c61e543
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'com.android.application'
+
+ext {
+ PUBLISH_VERSION = '1.2.0'
+ PUBLISH_VERSION_CODE = 3
+ SUPPORT_VERSION = '25.1.0'
+ TARGET_SDK = 24
+ MIN_SDK = 10
+ BUILD_TOOLS = "24.0.3"
+}
+
+android {
+ compileSdkVersion TARGET_SDK
+ buildToolsVersion BUILD_TOOLS
+
+ defaultConfig {
+ minSdkVersion MIN_SDK
+ targetSdkVersion TARGET_SDK
+ versionCode PUBLISH_VERSION_CODE
+ versionName PUBLISH_VERSION
+ }
+}
+
+dependencies {
+ compile "com.android.support:appcompat-v7:$SUPPORT_VERSION"
+ compile 'com.jakewharton:butterknife:7.0.1'
+ compile project(':camerafragment')
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..aa55aa4
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/memfis/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6ef4e12
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/github/florent37/camerafragment/sample/MainActivity.java b/app/src/main/java/com/github/florent37/camerafragment/sample/MainActivity.java
new file mode 100644
index 0000000..4cfb750
--- /dev/null
+++ b/app/src/main/java/com/github/florent37/camerafragment/sample/MainActivity.java
@@ -0,0 +1,333 @@
+package com.github.florent37.camerafragment.sample;
+
+import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.RequiresPermission;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.TextView;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import com.github.florent37.camerafragment.CameraFragment;
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.PreviewActivity;
+import com.github.florent37.camerafragment.listeners.CameraFragmentStateListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentControlsListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentResultListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentVideoRecordTextListener;
+import com.github.florent37.camerafragment.widgets.CameraSettingsView;
+import com.github.florent37.camerafragment.widgets.CameraSwitchView;
+import com.github.florent37.camerafragment.widgets.FlashSwitchView;
+import com.github.florent37.camerafragment.widgets.MediaActionSwitchView;
+import com.github.florent37.camerafragment.widgets.RecordButton;
+
+public class MainActivity extends AppCompatActivity {
+
+ private static final int REQUEST_CAMERA_PERMISSIONS = 931;
+ private static final int REQUEST_PREVIEW_CODE = 1001;
+
+ public static final String FRAGMENT_TAG = "camera";
+
+ @Bind(R.id.settings_view) CameraSettingsView settingsView;
+ @Bind(R.id.flash_switch_view) FlashSwitchView flashSwitchView;
+ @Bind(R.id.front_back_camera_switcher) CameraSwitchView cameraSwitchView;
+ @Bind(R.id.record_button) RecordButton recordButton;
+ @Bind(R.id.photo_video_camera_switcher) MediaActionSwitchView mediaActionSwitchView;
+
+ @Bind(R.id.record_duration_text) TextView recordDurationText;
+ @Bind(R.id.record_size_mb_text) TextView recordSizeText;
+
+ @Bind(R.id.cameraLayout) View cameraLayout;
+ @Bind(R.id.addCameraButton) View addCameraButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_fragment);
+ ButterKnife.bind(this);
+
+ recordButton.setVisibility(View.GONE);
+ settingsView.setVisibility(View.GONE);
+ flashSwitchView.setVisibility(View.GONE);
+ cameraSwitchView.setVisibility(View.GONE);
+ mediaActionSwitchView.setVisibility(View.GONE);
+
+ setupViewListeners();
+ }
+
+ @OnClick(R.id.take_photo)
+ public void takePhotoClicked(){
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.takePhotoOrCaptureVideo();
+ }
+ }
+
+ private void setupViewListeners() {
+ flashSwitchView.setFlashSwitchListener(new FlashSwitchView.FlashModeSwitchListener() {
+ @Override
+ public void toggleFlashMode() {
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.toggleFlashMode();
+ }
+ }
+ });
+
+ cameraSwitchView.setOnCameraTypeChangeListener(new CameraSwitchView.OnCameraTypeChangeListener() {
+ @Override
+ public void switchCameraType() {
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.switchCameraType();
+ }
+ }
+ });
+
+ recordButton.setRecordButtonListener(new RecordButton.RecordButtonListener() {
+ @Override
+ public void onRecordButtonClicked() {
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.takePhotoOrCaptureVideo();
+ }
+ }
+ });
+
+ mediaActionSwitchView.setOnMediaActionStateChangeListener(new MediaActionSwitchView.OnMediaActionStateChangeListener() {
+ @Override
+ public void switchAction() {
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.switchAction();
+ }
+ }
+ });
+
+ settingsView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final CameraFragment cameraFragment = getCameraFragment();
+ if (cameraFragment != null) {
+ cameraFragment.openSettingDialog();
+ }
+ }
+ });
+ }
+
+ @OnClick(R.id.addCameraButton)
+ public void onAddCameraClicked(){
+ if (Build.VERSION.SDK_INT > 15) {
+ final String[] permissions = {
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE};
+
+ final List permissionsToRequest = new ArrayList<>();
+ for (String permission : permissions) {
+ if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
+ permissionsToRequest.add(permission);
+ }
+ }
+ if (!permissionsToRequest.isEmpty()) {
+ ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[permissionsToRequest.size()]), REQUEST_CAMERA_PERMISSIONS);
+ } else addCamera();
+ } else {
+ addCamera();
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults.length != 0) {
+ addCamera();
+ }
+ }
+
+ @RequiresPermission(Manifest.permission.CAMERA)
+ public void addCamera() {
+ addCameraButton.setVisibility(View.GONE);
+ cameraLayout.setVisibility(View.VISIBLE);
+
+ final CameraFragment cameraFragment = CameraFragment.newInstance(new Configuration.Builder().build());
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, cameraFragment, FRAGMENT_TAG)
+ .commit();
+
+ if (cameraFragment != null) {
+
+ cameraFragment.setResultListener(new CameraFragmentResultListener() {
+ @Override
+ public void onVideoRecorded(String filePath) {
+ Intent intent = PreviewActivity.newIntentVideo(MainActivity.this, filePath);
+ startActivityForResult(intent, REQUEST_PREVIEW_CODE);
+ }
+
+ @Override
+ public void onPhotoTaken(byte[] bytes, String filePath) {
+ Intent intent = PreviewActivity.newIntentPhoto(MainActivity.this, filePath);
+ startActivityForResult(intent, REQUEST_PREVIEW_CODE);
+ }
+ });
+
+ cameraFragment.setStateListener(new CameraFragmentStateListener() {
+
+ @Override
+ public void onCurrentCameraBack() {
+ cameraSwitchView.displayBackCamera();
+ }
+
+ @Override
+ public void onCurrentCameraFront() {
+ cameraSwitchView.displayFrontCamera();
+ }
+
+ @Override
+ public void onFlashAuto() {
+ flashSwitchView.displayFlashAuto();
+ }
+
+ @Override
+ public void onFlashOn() {
+ flashSwitchView.displayFlashOn();
+ }
+
+ @Override
+ public void onFlashOff() {
+ flashSwitchView.displayFlashOff();
+ }
+
+ @Override
+ public void onCameraSetupForPhoto() {
+ mediaActionSwitchView.displayActionWillSwitchVideo();
+
+ recordButton.displayPhotoState();
+ flashSwitchView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onCameraSetupForVideo() {
+ mediaActionSwitchView.displayActionWillSwitchPhoto();
+
+ recordButton.displayVideoRecordStateReady();
+ flashSwitchView.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void shouldRotateControls(int degrees) {
+ ViewCompat.setRotation(cameraSwitchView, degrees);
+ ViewCompat.setRotation(mediaActionSwitchView, degrees);
+ ViewCompat.setRotation(flashSwitchView, degrees);
+ ViewCompat.setRotation(recordDurationText, degrees);
+ ViewCompat.setRotation(recordSizeText, degrees);
+ }
+
+ @Override
+ public void onRecordStateVideoReadyForRecord() {
+ recordButton.displayVideoRecordStateReady();
+ }
+
+ @Override
+ public void onRecordStateVideoInProgress() {
+ recordButton.displayVideoRecordStateInProgress();
+ }
+
+ @Override
+ public void onRecordStatePhoto() {
+ recordButton.displayPhotoState();
+ }
+
+ @Override
+ public void onStopVideoRecord() {
+ recordSizeText.setVisibility(View.GONE);
+ //cameraSwitchView.setVisibility(View.VISIBLE);
+ settingsView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onStartVideoRecord(File outputFile) {
+ }
+ });
+
+ cameraFragment.setControlsListener(new CameraFragmentControlsListener() {
+ @Override
+ public void lockControls() {
+ cameraSwitchView.setEnabled(false);
+ recordButton.setEnabled(false);
+ settingsView.setEnabled(false);
+ flashSwitchView.setEnabled(false);
+ }
+
+ @Override
+ public void unLockControls() {
+ cameraSwitchView.setEnabled(true);
+ recordButton.setEnabled(true);
+ settingsView.setEnabled(true);
+ flashSwitchView.setEnabled(true);
+ }
+
+ @Override
+ public void allowCameraSwitching(boolean allow) {
+ //cameraSwitchView.setVisibility(allow ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void allowRecord(boolean allow) {
+ recordButton.setEnabled(allow);
+ }
+
+ @Override
+ public void setMediaActionSwitchVisible(boolean visible) {
+ //mediaActionSwitchView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+
+ cameraFragment.setTextListener(new CameraFragmentVideoRecordTextListener() {
+ @Override
+ public void setRecordSizeText(long size, String text) {
+ recordSizeText.setText(text);
+ }
+
+ @Override
+ public void setRecordSizeTextVisible(boolean visible) {
+ recordSizeText.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ public void setRecordDurationText(String text) {
+ recordDurationText.setText(text);
+ }
+
+ @Override
+ public void setRecordDurationTextVisible(boolean visible) {
+ recordDurationText.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ });
+
+
+ }
+
+ cameraSwitchView.displayBackCamera();
+ flashSwitchView.displayFlashAuto();
+ recordButton.displayPhotoState();
+ mediaActionSwitchView.displayActionWillSwitchVideo();
+ }
+
+ private CameraFragment getCameraFragment() {
+ return (CameraFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
+ }
+}
diff --git a/app/src/main/res/layout/activity_fragment.xml b/app/src/main/res/layout/activity_fragment.xml
new file mode 100644
index 0000000..7b73767
--- /dev/null
+++ b/app/src/main/res/layout/activity_fragment.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..269d1b6
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ CameraFragment
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..347b64a
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/art/default_camera.png b/art/default_camera.png
new file mode 100644
index 0000000..e107f70
Binary files /dev/null and b/art/default_camera.png differ
diff --git a/art/settings_for_video_limitation.png b/art/settings_for_video_limitation.png
new file mode 100644
index 0000000..4e01890
Binary files /dev/null and b/art/settings_for_video_limitation.png differ
diff --git a/art/video_camera.png b/art/video_camera.png
new file mode 100644
index 0000000..63e3a06
Binary files /dev/null and b/art/video_camera.png differ
diff --git a/art/video_low_quality.png b/art/video_low_quality.png
new file mode 100644
index 0000000..1b58f69
Binary files /dev/null and b/art/video_low_quality.png differ
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..e894cea
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,22 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.2.3'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/camerafragment/.gitignore b/camerafragment/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/camerafragment/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/camerafragment/build.gradle b/camerafragment/build.gradle
new file mode 100644
index 0000000..096bcab
--- /dev/null
+++ b/camerafragment/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.library'
+
+
+ext {
+ PUBLISH_VERSION = '0.3.2'
+ PUBLISH_VERSION_CODE = 6
+ SUPPORT_VERSION = '25.1.0'
+ TARGET_SDK = 24
+ MIN_SDK = 10
+ BUILD_TOOLS = "24.0.3"
+}
+
+android {
+ compileSdkVersion TARGET_SDK
+ buildToolsVersion BUILD_TOOLS
+
+ defaultConfig {
+ minSdkVersion MIN_SDK
+ targetSdkVersion TARGET_SDK
+ versionCode PUBLISH_VERSION_CODE
+ versionName PUBLISH_VERSION
+ }
+}
+
+dependencies {
+ compile "com.android.support:support-v4:$SUPPORT_VERSION"
+ compile "com.android.support:appcompat-v7:$SUPPORT_VERSION"
+}
+
+task wrapper(type: Wrapper) {
+ gradleVersion = '2.4'
+}
\ No newline at end of file
diff --git a/camerafragment/proguard-rules.pro b/camerafragment/proguard-rules.pro
new file mode 100644
index 0000000..aa55aa4
--- /dev/null
+++ b/camerafragment/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/memfis/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/camerafragment/src/main/AndroidManifest.xml b/camerafragment/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b8369b8
--- /dev/null
+++ b/camerafragment/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/CameraFragment.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/CameraFragment.java
new file mode 100644
index 0000000..5bc12b5
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/CameraFragment.java
@@ -0,0 +1,15 @@
+package com.github.florent37.camerafragment;
+
+import android.Manifest;
+import android.support.annotation.RequiresPermission;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.internal.ui.BaseAnncaFragment;
+
+public class CameraFragment extends BaseAnncaFragment {
+
+ @RequiresPermission(Manifest.permission.CAMERA)
+ public static CameraFragment newInstance(Configuration configuration) {
+ return (CameraFragment) BaseAnncaFragment.newInstance(new CameraFragment(), configuration);
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/PreviewActivity.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/PreviewActivity.java
new file mode 100644
index 0000000..d343d97
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/PreviewActivity.java
@@ -0,0 +1,386 @@
+package com.github.florent37.camerafragment;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.MediaController;
+import android.widget.TextView;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.internal.enums.MediaAction;
+import com.github.florent37.camerafragment.internal.ui.view.AspectFrameLayout;
+import com.github.florent37.camerafragment.internal.utils.ImageLoader;
+import com.github.florent37.camerafragment.internal.utils.Utils;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public class PreviewActivity extends AppCompatActivity implements View.OnClickListener {
+
+ private static final String TAG = "PreviewActivity";
+
+ public static final int ACTION_CONFIRM = 900;
+ public static final int ACTION_RETAKE = 901;
+ public static final int ACTION_CANCEL = 902;
+
+ private final static String MEDIA_ACTION_ARG = "media_action_arg";
+ private final static String FILE_PATH_ARG = "file_path_arg";
+ private final static String RESPONSE_CODE_ARG = "response_code_arg";
+ private final static String VIDEO_POSITION_ARG = "current_video_position";
+ private final static String VIDEO_IS_PLAYED_ARG = "is_played";
+ private final static String MIME_TYPE_VIDEO = "video";
+ private final static String MIME_TYPE_IMAGE = "image";
+
+ private int mediaAction;
+ private String previewFilePath;
+
+ private SurfaceView surfaceView;
+ private FrameLayout photoPreviewContainer;
+ private ImageView imagePreview;
+ private ViewGroup buttonPanel;
+ private AspectFrameLayout videoPreviewContainer;
+ private View cropMediaAction;
+ private TextView ratioChanger;
+
+ private MediaController mediaController;
+ private MediaPlayer mediaPlayer;
+
+ private int currentPlaybackPosition = 0;
+ private boolean isVideoPlaying = true;
+
+ private int currentRatioIndex = 0;
+ private float[] ratios;
+ private String[] ratioLabels;
+
+ private SurfaceHolder.Callback surfaceCallbacks = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ showVideoPreview(holder);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+
+ }
+ };
+
+ private MediaController.MediaPlayerControl MediaPlayerControlImpl = new MediaController.MediaPlayerControl() {
+ @Override
+ public void start() {
+ mediaPlayer.start();
+ }
+
+ @Override
+ public void pause() {
+ mediaPlayer.pause();
+ }
+
+ @Override
+ public int getDuration() {
+ return mediaPlayer.getDuration();
+ }
+
+ @Override
+ public int getCurrentPosition() {
+ return mediaPlayer.getCurrentPosition();
+ }
+
+ @Override
+ public void seekTo(int pos) {
+ mediaPlayer.seekTo(pos);
+ }
+
+ @Override
+ public boolean isPlaying() {
+ return mediaPlayer.isPlaying();
+ }
+
+ @Override
+ public int getBufferPercentage() {
+ return 0;
+ }
+
+ @Override
+ public boolean canPause() {
+ return true;
+ }
+
+ @Override
+ public boolean canSeekBackward() {
+ return true;
+ }
+
+ @Override
+ public boolean canSeekForward() {
+ return true;
+ }
+
+ @Override
+ public int getAudioSessionId() {
+ return mediaPlayer.getAudioSessionId();
+ }
+ };
+
+ public static Intent newIntentPhoto(Context context,
+ String filePath) {
+
+ return new Intent(context, PreviewActivity.class)
+ .putExtra(MEDIA_ACTION_ARG, MediaAction.ACTION_PHOTO)
+ .putExtra(FILE_PATH_ARG, filePath);
+ }
+
+ public static Intent newIntentVideo(Context context,
+ String filePath) {
+
+ return new Intent(context, PreviewActivity.class)
+ .putExtra(MEDIA_ACTION_ARG, MediaAction.ACTION_VIDEO)
+ .putExtra(FILE_PATH_ARG, filePath);
+ }
+
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_preview);
+
+ String originalRatioLabel = getString(R.string.preview_controls_original_ratio_label);
+ ratioLabels = new String[]{originalRatioLabel, "1:1", "4:3", "16:9"};
+ ratios = new float[]{0f, 1f, 4f / 3f, 16f / 9f};
+
+ surfaceView = (SurfaceView) findViewById(R.id.video_preview);
+ surfaceView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mediaController == null) return false;
+ if (mediaController.isShowing()) {
+ mediaController.hide();
+ showButtonPanel(true);
+ } else {
+ showButtonPanel(false);
+ mediaController.show();
+ }
+ return false;
+ }
+ });
+
+ videoPreviewContainer = (AspectFrameLayout) findViewById(R.id.previewAspectFrameLayout);
+ photoPreviewContainer = (FrameLayout) findViewById(R.id.photo_preview_container);
+ buttonPanel = (ViewGroup) findViewById(R.id.preview_control_panel);
+ View confirmMediaResult = findViewById(R.id.confirm_media_result);
+ View reTakeMedia = findViewById(R.id.re_take_media);
+ View cancelMediaAction = findViewById(R.id.cancel_media_action);
+ cropMediaAction = findViewById(R.id.crop_image);
+ ratioChanger = (TextView) findViewById(R.id.ratio_image);
+ ratioChanger.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ currentRatioIndex = (currentRatioIndex + 1) % ratios.length;
+ ratioChanger.setText(ratioLabels[currentRatioIndex]);
+ }
+ });
+
+ cropMediaAction.setVisibility(View.GONE);
+ ratioChanger.setVisibility(View.GONE);
+
+ if (cropMediaAction != null)
+ cropMediaAction.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ }
+ });
+
+ if (confirmMediaResult != null)
+ confirmMediaResult.setOnClickListener(this);
+
+ if (reTakeMedia != null)
+ reTakeMedia.setOnClickListener(this);
+
+ if (cancelMediaAction != null)
+ cancelMediaAction.setOnClickListener(this);
+
+ Bundle args = getIntent().getExtras();
+
+ mediaAction = args.getInt(MEDIA_ACTION_ARG);
+ previewFilePath = args.getString(FILE_PATH_ARG);
+
+ if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) {
+ displayVideo(savedInstanceState);
+ } else if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) {
+ displayImage();
+ } else {
+ String mimeType = Utils.getMimeType(previewFilePath);
+ if (mimeType.contains(MIME_TYPE_VIDEO)) {
+ displayVideo(savedInstanceState);
+ } else if (mimeType.contains(MIME_TYPE_IMAGE)) {
+ displayImage();
+ } else finish();
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ saveVideoParams(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ if (mediaController != null) {
+ mediaController.hide();
+ mediaController = null;
+ }
+ }
+
+ private void displayImage() {
+ videoPreviewContainer.setVisibility(View.GONE);
+ surfaceView.setVisibility(View.GONE);
+ showImagePreview();
+ ratioChanger.setText(ratioLabels[currentRatioIndex]);
+ }
+
+ private void showImagePreview() {
+ imagePreview = new ImageView(this);
+ ImageLoader.Builder builder = new ImageLoader.Builder(this);
+ builder.load(previewFilePath).build().into(imagePreview);
+ photoPreviewContainer.removeAllViews();
+ photoPreviewContainer.addView(imagePreview);
+ }
+
+ private void displayVideo(Bundle savedInstanceState) {
+ cropMediaAction.setVisibility(View.GONE);
+ ratioChanger.setVisibility(View.GONE);
+ if (savedInstanceState != null) {
+ loadVideoParams(savedInstanceState);
+ }
+ photoPreviewContainer.setVisibility(View.GONE);
+ surfaceView.getHolder().addCallback(surfaceCallbacks);
+ }
+
+ private void showVideoPreview(SurfaceHolder holder) {
+ try {
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(previewFilePath);
+ mediaPlayer.setDisplay(holder);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ mediaController = new MediaController(PreviewActivity.this);
+ mediaController.setAnchorView(surfaceView);
+ mediaController.setMediaPlayer(MediaPlayerControlImpl);
+
+ int videoWidth = mp.getVideoWidth();
+ int videoHeight = mp.getVideoHeight();
+
+ videoPreviewContainer.setAspectRatio((double) videoWidth / videoHeight);
+
+ mediaPlayer.start();
+ mediaPlayer.seekTo(currentPlaybackPosition);
+
+ if (!isVideoPlaying)
+ mediaPlayer.pause();
+ }
+ });
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ finish();
+ return true;
+ }
+ });
+ mediaPlayer.prepareAsync();
+ } catch (Exception e) {
+ Log.e(TAG, "Error media player playing video.");
+ finish();
+ }
+ }
+
+ private void saveVideoParams(Bundle outState) {
+ if (mediaPlayer != null) {
+ outState.putInt(VIDEO_POSITION_ARG, mediaPlayer.getCurrentPosition());
+ outState.putBoolean(VIDEO_IS_PLAYED_ARG, mediaPlayer.isPlaying());
+ }
+ }
+
+ private void loadVideoParams(Bundle savedInstanceState) {
+ currentPlaybackPosition = savedInstanceState.getInt(VIDEO_POSITION_ARG, 0);
+ isVideoPlaying = savedInstanceState.getBoolean(VIDEO_IS_PLAYED_ARG, true);
+ }
+
+ private void showButtonPanel(boolean show) {
+ if (show) {
+ buttonPanel.setVisibility(View.VISIBLE);
+ } else {
+ buttonPanel.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ Intent resultIntent = new Intent();
+ if (view.getId() == R.id.confirm_media_result) {
+ resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_CONFIRM).putExtra(FILE_PATH_ARG, previewFilePath);
+ } else if (view.getId() == R.id.re_take_media) {
+ deleteMediaFile();
+ resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_RETAKE);
+ } else if (view.getId() == R.id.cancel_media_action) {
+ deleteMediaFile();
+ resultIntent.putExtra(RESPONSE_CODE_ARG, ACTION_CANCEL);
+ }
+ setResult(RESULT_OK, resultIntent);
+ finish();
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ deleteMediaFile();
+ }
+
+ private boolean deleteMediaFile() {
+ File mediaFile = new File(previewFilePath);
+ return mediaFile.delete();
+ }
+
+ public static String getMediaFilePatch(@NonNull Intent resultIntent) {
+ return resultIntent.getStringExtra(FILE_PATH_ARG);
+ }
+
+ public static boolean isResultConfirm(@NonNull Intent resultIntent) {
+ return ACTION_CONFIRM == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1);
+ }
+
+ public static boolean isResultRetake(@NonNull Intent resultIntent) {
+ return ACTION_RETAKE == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1);
+ }
+
+ public static boolean isResultCancel(@NonNull Intent resultIntent) {
+ return ACTION_CANCEL == resultIntent.getIntExtra(RESPONSE_CODE_ARG, -1);
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/Configuration.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/Configuration.java
new file mode 100644
index 0000000..7f861de
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/Configuration.java
@@ -0,0 +1,200 @@
+package com.github.florent37.camerafragment.configuration;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
+
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Created by memfis on 7/6/16.
+ * Updated by Florent37
+ */
+public final class Configuration implements Serializable {
+
+ public static final int MEDIA_QUALITY_AUTO = 10;
+ public static final int MEDIA_QUALITY_LOWEST = 15;
+ public static final int MEDIA_QUALITY_LOW = 11;
+ public static final int MEDIA_QUALITY_MEDIUM = 12;
+ public static final int MEDIA_QUALITY_HIGH = 13;
+ public static final int MEDIA_QUALITY_HIGHEST = 14;
+
+ public static final int MEDIA_ACTION_VIDEO = 100;
+ public static final int MEDIA_ACTION_PHOTO = 101;
+ public static final int MEDIA_ACTION_UNSPECIFIED = 102;
+
+ public static final int CAMERA_FACE_FRONT = 0x6;
+ public static final int CAMERA_FACE_REAR = 0x7;
+
+ public static final int SENSOR_POSITION_UP = 90;
+ public static final int SENSOR_POSITION_UP_SIDE_DOWN = 270;
+ public static final int SENSOR_POSITION_LEFT = 0;
+ public static final int SENSOR_POSITION_RIGHT = 180;
+ public static final int SENSOR_POSITION_UNSPECIFIED = -1;
+
+ public static final int DISPLAY_ROTATION_0 = 0;
+ public static final int DISPLAY_ROTATION_90 = 90;
+ public static final int DISPLAY_ROTATION_180 = 180;
+ public static final int DISPLAY_ROTATION_270 = 270;
+
+ public static final int ORIENTATION_PORTRAIT = 0x111;
+ public static final int ORIENTATION_LANDSCAPE = 0x222;
+
+ public static final int FLASH_MODE_ON = 1;
+ public static final int FLASH_MODE_OFF = 2;
+ public static final int FLASH_MODE_AUTO = 3;
+
+ public interface Arguments {
+ String REQUEST_CODE = "io.memfis19.annca.request_code";
+ String MEDIA_ACTION = "io.memfis19.annca.media_action";
+ String MEDIA_QUALITY = "io.memfis19.annca.camera_media_quality";
+ String VIDEO_DURATION = "io.memfis19.annca.video_duration";
+ String MINIMUM_VIDEO_DURATION = "io.memfis19.annca.minimum.video_duration";
+ String VIDEO_FILE_SIZE = "io.memfis19.annca.camera_video_file_size";
+ String FLASH_MODE = "io.memfis19.annca.camera_flash_mode";
+ String FILE_PATH = "io.memfis19.annca.camera_video_file_path";
+ }
+
+ @IntDef({MEDIA_QUALITY_AUTO, MEDIA_QUALITY_LOWEST, MEDIA_QUALITY_LOW, MEDIA_QUALITY_MEDIUM, MEDIA_QUALITY_HIGH, MEDIA_QUALITY_HIGHEST})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaQuality {
+ }
+
+ @IntDef({MEDIA_ACTION_VIDEO, MEDIA_ACTION_PHOTO, MEDIA_ACTION_UNSPECIFIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaAction {
+ }
+
+ @IntDef({FLASH_MODE_ON, FLASH_MODE_OFF, FLASH_MODE_AUTO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FlashMode {
+ }
+
+ @IntDef({CAMERA_FACE_FRONT, CAMERA_FACE_REAR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CameraFace {
+ }
+
+ @IntDef({SENSOR_POSITION_UP, SENSOR_POSITION_UP_SIDE_DOWN, SENSOR_POSITION_LEFT, SENSOR_POSITION_RIGHT, SENSOR_POSITION_UNSPECIFIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SensorPosition {
+ }
+
+ @IntDef({DISPLAY_ROTATION_0, DISPLAY_ROTATION_90, DISPLAY_ROTATION_180, DISPLAY_ROTATION_270})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisplayRotation {
+ }
+
+ @IntDef({ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceDefaultOrientation {
+ }
+
+ @MediaAction
+ private int mediaAction = -1;
+
+ @MediaQuality
+ private int mediaQuality = -1;
+
+ @CameraFace
+ private int cameraFace = -1;
+
+ private int videoDuration = -1;
+
+ private long videoFileSize = -1;
+
+ private int minimumVideoDuration = -1;
+
+ @FlashMode
+ private int flashMode = FLASH_MODE_AUTO;
+
+ public static class Builder {
+
+ private Configuration configuration;
+
+ public Builder() {
+ configuration = new Configuration();
+ }
+
+ public Builder setMediaAction(@MediaAction int mediaAction) {
+ configuration.mediaAction = mediaAction;
+ return this;
+ }
+
+ public Builder setMediaQuality(@MediaQuality int mediaQuality) {
+ configuration.mediaQuality = mediaQuality;
+ return this;
+ }
+
+ /***
+ * @param videoDurationInMilliseconds - video duration in milliseconds
+ * @return
+ */
+ public Builder setVideoDuration(@IntRange(from = 1000, to = Integer.MAX_VALUE) int videoDurationInMilliseconds) {
+ configuration.videoDuration = videoDurationInMilliseconds;
+ return this;
+ }
+
+ /***
+ * @param minimumVideoDurationInMilliseconds - minimum video duration in milliseconds, used only in video mode
+ * for auto quality.
+ * @return
+ */
+ public Builder setMinimumVideoDuration(@IntRange(from = 1000, to = Integer.MAX_VALUE) int minimumVideoDurationInMilliseconds) {
+ configuration.minimumVideoDuration = minimumVideoDurationInMilliseconds;
+ return this;
+ }
+
+ /***
+ * @param videoSizeInBytes - file size in bytes
+ * @return
+ */
+ public Builder setVideoFileSize(@IntRange(from = 1048576, to = Long.MAX_VALUE) long videoSizeInBytes) {
+ configuration.videoFileSize = videoSizeInBytes;
+ return this;
+ }
+
+ public Builder setFlashMode(@FlashMode int flashMode) {
+ configuration.flashMode = flashMode;
+ return this;
+ }
+
+ public Configuration build() throws IllegalArgumentException {
+ if (configuration.mediaQuality == MEDIA_QUALITY_AUTO && configuration.minimumVideoDuration < 0) {
+ throw new IllegalStateException("Please provide minimum video duration in milliseconds to use auto quality.");
+ }
+
+ return configuration;
+ }
+
+ }
+
+ public int getMediaAction() {
+ return mediaAction;
+ }
+
+ public int getMediaQuality() {
+ return mediaQuality;
+ }
+
+ public int getCameraFace() {
+ return cameraFace;
+ }
+
+ public int getVideoDuration() {
+ return videoDuration;
+ }
+
+ public long getVideoFileSize() {
+ return videoFileSize;
+ }
+
+ public int getMinimumVideoDuration() {
+ return minimumVideoDuration;
+ }
+
+ public int getFlashMode() {
+ return flashMode;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProvider.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProvider.java
new file mode 100644
index 0000000..01e7e11
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProvider.java
@@ -0,0 +1,53 @@
+package com.github.florent37.camerafragment.configuration;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public interface ConfigurationProvider {
+
+ @Configuration.MediaAction
+ int getMediaAction();
+
+ @Configuration.MediaQuality
+ int getMediaQuality();
+
+ int getVideoDuration();
+
+ long getVideoFileSize();
+
+ @Configuration.SensorPosition
+ int getSensorPosition();
+
+ int getDegrees();
+
+ int getMinimumVideoDuration();
+
+ @Configuration.FlashMode
+ int getFlashMode();
+
+ void setMediaQuality(int mediaQuality);
+
+ void setPassedMediaQuality(int mediaQuality);
+
+ void setVideoDuration(int videoDuration);
+
+ void setVideoFileSize(long videoFileSize);
+
+ void setMinimumVideoDuration(int minimumVideoDuration);
+
+ void setFlashMode(int flashMode);
+
+ void setSensorPosition(int sensorPosition);
+
+ int getDeviceDefaultOrientation();
+
+ void setDegrees(int degrees);
+
+ void setMediaAction(int mediaAction);
+
+ void setDeviceDefaultOrientation(int deviceDefaultOrientation);
+
+ int getPassedMediaQuality();
+
+ void setupWithAnnaConfiguration(Configuration configuration);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProviderImpl.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProviderImpl.java
new file mode 100644
index 0000000..cbb8a64
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/configuration/ConfigurationProviderImpl.java
@@ -0,0 +1,207 @@
+package com.github.florent37.camerafragment.configuration;
+
+/**
+ * Created by florentchampigny on 12/01/2017.
+ */
+
+public class ConfigurationProviderImpl implements ConfigurationProvider {
+
+ protected long videoFileSize = -1;
+ protected int videoDuration = -1;
+ protected int minimumVideoDuration = -1;
+
+ @Configuration.MediaAction
+ protected int mediaAction = Configuration.MEDIA_ACTION_UNSPECIFIED;
+
+ @Configuration.MediaQuality
+ protected int mediaQuality = Configuration.MEDIA_QUALITY_MEDIUM;
+
+ @Configuration.MediaQuality
+ protected int passedMediaQuality = Configuration.MEDIA_QUALITY_MEDIUM;
+
+ @Configuration.FlashMode
+ protected int flashMode = Configuration.FLASH_MODE_AUTO;
+
+ @Configuration.SensorPosition
+ protected int sensorPosition = Configuration.SENSOR_POSITION_UNSPECIFIED;
+
+ @Configuration.DeviceDefaultOrientation
+ protected int deviceDefaultOrientation;
+
+ private int degrees = -1;
+
+ @Override
+ public int getMediaAction() {
+ return mediaAction;
+ }
+
+ @Override
+ public int getMediaQuality() {
+ return mediaQuality;
+ }
+
+ @Override
+ public int getVideoDuration() {
+ return videoDuration;
+ }
+
+ @Override
+ public long getVideoFileSize() {
+ return videoFileSize;
+ }
+
+ @Override
+ public int getMinimumVideoDuration() {
+ return minimumVideoDuration / 1000;
+ }
+
+ @Override
+ public final int getSensorPosition() {
+ return sensorPosition;
+ }
+
+ @Override
+ public final int getDegrees() {
+ return degrees;
+ }
+
+ @Override
+ public int getFlashMode() {
+ return flashMode;
+ }
+
+ @Override
+ public void setMediaQuality(int mediaQuality) {
+ this.mediaQuality = mediaQuality;
+ }
+
+ @Override
+ public void setPassedMediaQuality(int mediaQuality) {
+ this.passedMediaQuality = mediaQuality;
+ }
+
+ @Override
+ public void setVideoDuration(int videoDuration) {
+ this.videoDuration = videoDuration;
+ }
+
+ @Override
+ public void setVideoFileSize(long videoFileSize) {
+ this.videoFileSize = videoFileSize;
+ }
+
+ @Override
+ public void setMinimumVideoDuration(int minimumVideoDuration) {
+ this.minimumVideoDuration = minimumVideoDuration;
+ }
+
+ @Override
+ public void setFlashMode(int flashMode) {
+ this.flashMode = flashMode;
+ }
+
+ public void setMediaAction(int mediaAction) {
+ this.mediaAction = mediaAction;
+ }
+
+ public int getPassedMediaQuality() {
+ return passedMediaQuality;
+ }
+
+ @Override
+ public void setupWithAnnaConfiguration(Configuration configuration) {
+ if (configuration != null) {
+
+ final int mediaAction = configuration.getMediaAction();
+ if (mediaAction != -1) {
+ switch (mediaAction) {
+ case Configuration.MEDIA_ACTION_PHOTO:
+ setMediaAction(Configuration.MEDIA_ACTION_PHOTO);
+ break;
+ case Configuration.MEDIA_ACTION_VIDEO:
+ setMediaAction(Configuration.MEDIA_ACTION_VIDEO);
+ break;
+ default:
+ setMediaAction(Configuration.MEDIA_ACTION_UNSPECIFIED);
+ break;
+ }
+ }
+
+ final int mediaQuality = configuration.getMediaQuality();
+ if (mediaQuality != -1) {
+ switch (mediaQuality) {
+ case Configuration.MEDIA_QUALITY_AUTO:
+ setMediaQuality(Configuration.MEDIA_QUALITY_AUTO);
+ break;
+ case Configuration.MEDIA_QUALITY_HIGHEST:
+ setMediaQuality(Configuration.MEDIA_QUALITY_HIGHEST);
+ break;
+ case Configuration.MEDIA_QUALITY_HIGH:
+ setMediaQuality(Configuration.MEDIA_QUALITY_HIGH);
+ break;
+ case Configuration.MEDIA_QUALITY_MEDIUM:
+ setMediaQuality(Configuration.MEDIA_QUALITY_MEDIUM);
+ break;
+ case Configuration.MEDIA_QUALITY_LOW:
+ setMediaQuality(Configuration.MEDIA_QUALITY_LOW);
+ break;
+ case Configuration.MEDIA_QUALITY_LOWEST:
+ setMediaQuality(Configuration.MEDIA_QUALITY_LOWEST);
+ break;
+ default:
+ setMediaQuality(Configuration.MEDIA_QUALITY_MEDIUM);
+ break;
+ }
+ setPassedMediaQuality(getMediaQuality());
+ }
+
+ final int videoDuration = configuration.getVideoDuration();
+ if (videoDuration != -1) {
+ setVideoDuration(videoDuration);
+ }
+
+ final long videoFileSize = configuration.getVideoFileSize();
+ if (videoFileSize != -1) {
+ setVideoFileSize(videoFileSize);
+ }
+
+ final int minimumVideoDuration = configuration.getMinimumVideoDuration();
+ if (minimumVideoDuration != -1) {
+ setMinimumVideoDuration(minimumVideoDuration);
+ }
+
+ final int flashMode = configuration.getFlashMode();
+ if (flashMode != -1)
+ switch (flashMode) {
+ case Configuration.FLASH_MODE_AUTO:
+ setFlashMode(Configuration.FLASH_MODE_AUTO);
+ break;
+ case Configuration.FLASH_MODE_ON:
+ setFlashMode(Configuration.FLASH_MODE_ON);
+ break;
+ case Configuration.FLASH_MODE_OFF:
+ setFlashMode(Configuration.FLASH_MODE_OFF);
+ break;
+ default:
+ setFlashMode(Configuration.FLASH_MODE_AUTO);
+ break;
+ }
+ }
+ }
+
+ public void setSensorPosition(int sensorPosition) {
+ this.sensorPosition = sensorPosition;
+ }
+
+ public int getDeviceDefaultOrientation() {
+ return deviceDefaultOrientation;
+ }
+
+ public void setDeviceDefaultOrientation(int deviceDefaultOrientation) {
+ this.deviceDefaultOrientation = deviceDefaultOrientation;
+ }
+
+ public void setDegrees(int degrees) {
+ this.degrees = degrees;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/CameraController.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/CameraController.java
new file mode 100644
index 0000000..29ad2a4
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/CameraController.java
@@ -0,0 +1,51 @@
+package com.github.florent37.camerafragment.internal.controller;
+
+import android.os.Bundle;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.internal.manager.CameraManager;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public interface CameraController {
+
+ void onCreate(Bundle savedInstanceState);
+
+ void onResume();
+
+ void onPause();
+
+ void onDestroy();
+
+ void takePhoto();
+
+ void startVideoRecord();
+
+ void stopVideoRecord();
+
+ boolean isVideoRecording();
+
+ void switchCamera(@Configuration.CameraFace int cameraFace);
+
+ void switchQuality();
+
+ void setFlashMode(@Configuration.FlashMode int flashMode);
+
+ int getNumberOfCameras();
+
+ @Configuration.MediaAction
+ int getMediaAction();
+
+ CameraId getCurrentCameraId();
+
+ File getOutputFile();
+
+ CameraManager getCameraManager();
+
+ CharSequence[] getVideoQualityOptions();
+
+ CharSequence[] getPhotoQualityOptions();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera1Controller.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera1Controller.java
new file mode 100644
index 0000000..159866b
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera1Controller.java
@@ -0,0 +1,194 @@
+package com.github.florent37.camerafragment.internal.controller.impl;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.controller.CameraController;
+import com.github.florent37.camerafragment.internal.controller.view.CameraView;
+import com.github.florent37.camerafragment.internal.manager.CameraManager;
+import com.github.florent37.camerafragment.internal.manager.impl.Camera1Manager;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener;
+import com.github.florent37.camerafragment.internal.ui.view.AutoFitSurfaceView;
+import com.github.florent37.camerafragment.internal.utils.CameraHelper;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 7/7/16.
+ */
+
+@SuppressWarnings("deprecation")
+public class Camera1Controller implements CameraController,
+ CameraOpenListener, CameraPhotoListener, CameraCloseListener, CameraVideoListener {
+
+ private final static String TAG = "Camera1Controller";
+
+ private final Context context;
+
+ private Integer currentCameraId;
+ private ConfigurationProvider configurationProvider;
+ private CameraManager cameraManager;
+ private CameraView cameraView;
+
+ private File outputFile;
+
+ public Camera1Controller(Context context, CameraView cameraView, ConfigurationProvider configurationProvider) {
+ this.context = context;
+ this.cameraView = cameraView;
+ this.configurationProvider = configurationProvider;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ cameraManager = Camera1Manager.getInstance();
+ cameraManager.initializeCameraManager(configurationProvider, context);
+ setCurrentCameraId(cameraManager.getFaceBackCameraId());
+ }
+
+ private void setCurrentCameraId(Integer cameraId) {
+ this.currentCameraId = cameraId;
+ cameraManager.setCameraId(cameraId);
+ }
+
+ @Override
+ public void onResume() {
+ cameraManager.openCamera(currentCameraId, this);
+ }
+
+ @Override
+ public void onPause() {
+ cameraManager.closeCamera(null);
+ }
+
+ @Override
+ public void onDestroy() {
+ cameraManager.releaseCameraManager();
+ }
+
+ @Override
+ public void takePhoto() {
+ outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_PHOTO);
+ cameraManager.takePhoto(outputFile, this);
+ }
+
+ @Override
+ public void startVideoRecord() {
+ outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_VIDEO);
+ cameraManager.startVideoRecord(outputFile, this);
+ }
+
+ @Override
+ public void stopVideoRecord() {
+ cameraManager.stopVideoRecord();
+ }
+
+ @Override
+ public boolean isVideoRecording() {
+ return cameraManager.isVideoRecording();
+ }
+
+ @Override
+ public void switchCamera(@Configuration.CameraFace final int cameraFace) {
+ setCurrentCameraId(cameraManager.getCurrentCameraId().equals(cameraManager.getFaceFrontCameraId()) ?
+ cameraManager.getFaceBackCameraId() : cameraManager.getFaceFrontCameraId());
+
+ cameraManager.closeCamera(this);
+ }
+
+ @Override
+ public void setFlashMode(@Configuration.FlashMode int flashMode) {
+ cameraManager.setFlashMode(flashMode);
+ }
+
+ @Override
+ public void switchQuality() {
+ cameraManager.closeCamera(this);
+ }
+
+ @Override
+ public int getNumberOfCameras() {
+ return cameraManager.getNumberOfCameras();
+ }
+
+ @Override
+ public int getMediaAction() {
+ return configurationProvider.getMediaAction();
+ }
+
+ @Override
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @Override
+ public Integer getCurrentCameraId() {
+ return currentCameraId;
+ }
+
+
+ @Override
+ public void onCameraOpened(Integer cameraId, Size previewSize, SurfaceHolder.Callback surfaceCallback) {
+ cameraView.updateUiForMediaAction(configurationProvider.getMediaAction());
+ cameraView.updateCameraPreview(previewSize, new AutoFitSurfaceView(context, surfaceCallback));
+ cameraView.updateCameraSwitcher(getNumberOfCameras());
+ }
+
+ @Override
+ public void onCameraOpenError() {
+ Log.e(TAG, "onCameraOpenError");
+ }
+
+ @Override
+ public void onCameraClosed(Integer closedCameraId) {
+ cameraView.releaseCameraPreview();
+
+ cameraManager.openCamera(currentCameraId, this);
+ }
+
+ @Override
+ public void onPhotoTaken(byte[] bytes, File photoFile) {
+ cameraView.onPhotoTaken(bytes);
+ }
+
+ @Override
+ public void onPhotoTakeError() {
+ }
+
+ @Override
+ public void onVideoRecordStarted(Size videoSize) {
+ cameraView.onVideoRecordStart(videoSize.getWidth(), videoSize.getHeight());
+ }
+
+ @Override
+ public void onVideoRecordStopped(File videoFile) {
+ cameraView.onVideoRecordStop();
+ }
+
+ @Override
+ public void onVideoRecordError() {
+
+ }
+
+ @Override
+ public CameraManager getCameraManager() {
+ return cameraManager;
+ }
+
+ @Override
+ public CharSequence[] getVideoQualityOptions() {
+ return cameraManager.getVideoQualityOptions();
+ }
+
+ @Override
+ public CharSequence[] getPhotoQualityOptions() {
+ return cameraManager.getPhotoQualityOptions();
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera2Controller.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera2Controller.java
new file mode 100644
index 0000000..081ec13
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/impl/Camera2Controller.java
@@ -0,0 +1,195 @@
+package com.github.florent37.camerafragment.internal.controller.impl;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.TextureView;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.controller.CameraController;
+import com.github.florent37.camerafragment.internal.controller.view.CameraView;
+import com.github.florent37.camerafragment.internal.manager.CameraManager;
+import com.github.florent37.camerafragment.internal.manager.impl.Camera2Manager;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener;
+import com.github.florent37.camerafragment.internal.ui.view.AutoFitTextureView;
+import com.github.florent37.camerafragment.internal.utils.CameraHelper;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class Camera2Controller implements CameraController,
+ CameraOpenListener,
+ CameraPhotoListener, CameraVideoListener, CameraCloseListener {
+
+ private final static String TAG = "Camera2Controller";
+
+ private final Context context;
+ private String currentCameraId;
+ private ConfigurationProvider configurationProvider;
+ private CameraManager camera2Manager;
+ private CameraView cameraView;
+
+ private File outputFile;
+
+ public Camera2Controller(Context context, CameraView cameraView, ConfigurationProvider configurationProvider) {
+ this.context = context;
+ this.cameraView = cameraView;
+ this.configurationProvider = configurationProvider;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ camera2Manager = Camera2Manager.getInstance();
+ camera2Manager.initializeCameraManager(configurationProvider, context);
+ setCurrentCameraId(camera2Manager.getFaceBackCameraId());
+ }
+
+ @Override
+ public void onResume() {
+ camera2Manager.openCamera(currentCameraId, this);
+ }
+
+ @Override
+ public void onPause() {
+ camera2Manager.closeCamera(null);
+ cameraView.releaseCameraPreview();
+ }
+
+ @Override
+ public void onDestroy() {
+ camera2Manager.releaseCameraManager();
+ }
+
+ @Override
+ public void takePhoto() {
+ outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_PHOTO);
+ camera2Manager.takePhoto(outputFile, this);
+ }
+
+ @Override
+ public void startVideoRecord() {
+ outputFile = CameraHelper.getOutputMediaFile(context, Configuration.MEDIA_ACTION_VIDEO);
+ camera2Manager.startVideoRecord(outputFile, this);
+ }
+
+ @Override
+ public void stopVideoRecord() {
+ camera2Manager.stopVideoRecord();
+ }
+
+ @Override
+ public boolean isVideoRecording() {
+ return camera2Manager.isVideoRecording();
+ }
+
+ @Override
+ public void switchCamera(final @Configuration.CameraFace int cameraFace) {
+ setCurrentCameraId(camera2Manager.getCurrentCameraId().equals(camera2Manager.getFaceFrontCameraId()) ?
+ camera2Manager.getFaceBackCameraId() : camera2Manager.getFaceFrontCameraId());
+
+ camera2Manager.closeCamera(this);
+ }
+
+ private void setCurrentCameraId(String currentCameraId){
+ this.currentCameraId = currentCameraId;
+ camera2Manager.setCameraId(currentCameraId);
+ }
+
+ @Override
+ public void setFlashMode(@Configuration.FlashMode int flashMode) {
+ camera2Manager.setFlashMode(flashMode);
+ }
+
+ @Override
+ public void switchQuality() {
+ camera2Manager.closeCamera(this);
+ }
+
+ @Override
+ public int getNumberOfCameras() {
+ return camera2Manager.getNumberOfCameras();
+ }
+
+ @Override
+ public int getMediaAction() {
+ return configurationProvider.getMediaAction();
+ }
+
+ @Override
+ public File getOutputFile() {
+ return outputFile;
+ }
+
+ @Override
+ public String getCurrentCameraId() {
+ return currentCameraId;
+ }
+
+ @Override
+ public void onCameraOpened(String openedCameraId, Size previewSize, TextureView.SurfaceTextureListener surfaceTextureListener) {
+ cameraView.updateUiForMediaAction(Configuration.MEDIA_ACTION_UNSPECIFIED);
+ cameraView.updateCameraPreview(previewSize, new AutoFitTextureView(context, surfaceTextureListener));
+ cameraView.updateCameraSwitcher(camera2Manager.getNumberOfCameras());
+ }
+
+ @Override
+ public void onCameraOpenError() {
+ Log.e(TAG, "onCameraOpenError");
+ }
+
+ @Override
+ public void onCameraClosed(String closedCameraId) {
+ cameraView.releaseCameraPreview();
+
+ camera2Manager.openCamera(currentCameraId, this);
+ }
+
+ @Override
+ public void onPhotoTaken(byte[] bytes, File photoFile) {
+ cameraView.onPhotoTaken(bytes);
+ }
+
+ @Override
+ public void onPhotoTakeError() {
+ }
+
+ @Override
+ public void onVideoRecordStarted(Size videoSize) {
+ cameraView.onVideoRecordStart(videoSize.getWidth(), videoSize.getHeight());
+ }
+
+ @Override
+ public void onVideoRecordStopped(File videoFile) {
+ cameraView.onVideoRecordStop();
+ }
+
+ @Override
+ public void onVideoRecordError() {
+
+ }
+
+ @Override
+ public CameraManager getCameraManager() {
+ return camera2Manager;
+ }
+
+ @Override
+ public CharSequence[] getVideoQualityOptions() {
+ return camera2Manager.getVideoQualityOptions();
+ }
+
+ @Override
+ public CharSequence[] getPhotoQualityOptions() {
+ return camera2Manager.getPhotoQualityOptions();
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/view/CameraView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/view/CameraView.java
new file mode 100644
index 0000000..9a6adb5
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/controller/view/CameraView.java
@@ -0,0 +1,27 @@
+package com.github.florent37.camerafragment.internal.controller.view;
+
+import android.view.View;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public interface CameraView {
+
+ void updateCameraPreview(Size size, View cameraPreview);
+
+ void updateUiForMediaAction(@Configuration.MediaAction int mediaAction);
+
+ void updateCameraSwitcher(int numberOfCameras);
+
+ void onPhotoTaken(byte[] bytes);
+
+ void onVideoRecordStart(int width, int height);
+
+ void onVideoRecordStop();
+
+ void releaseCameraPreview();
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Camera.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Camera.java
new file mode 100644
index 0000000..8503135
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Camera.java
@@ -0,0 +1,17 @@
+package com.github.florent37.camerafragment.internal.enums;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class Camera {
+
+ public static final int CAMERA_TYPE_FRONT = 0;
+ public static final int CAMERA_TYPE_REAR = 1;
+
+ @IntDef({CAMERA_TYPE_FRONT, CAMERA_TYPE_REAR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CameraType {
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Flash.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Flash.java
new file mode 100644
index 0000000..012cb59
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Flash.java
@@ -0,0 +1,17 @@
+package com.github.florent37.camerafragment.internal.enums;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class Flash {
+ public static final int FLASH_ON = 0;
+ public static final int FLASH_OFF = 1;
+ public static final int FLASH_AUTO = 2;
+
+ @IntDef({FLASH_ON, FLASH_OFF, FLASH_AUTO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FlashMode {
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/MediaAction.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/MediaAction.java
new file mode 100644
index 0000000..22d5842
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/MediaAction.java
@@ -0,0 +1,17 @@
+package com.github.florent37.camerafragment.internal.enums;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class MediaAction {
+
+ public final static int ACTION_PHOTO = 0;
+ public final static int ACTION_VIDEO = 1;
+
+ @IntDef({ACTION_PHOTO, ACTION_VIDEO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MediaActionState {
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Record.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Record.java
new file mode 100644
index 0000000..96af011
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/enums/Record.java
@@ -0,0 +1,19 @@
+package com.github.florent37.camerafragment.internal.enums;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class Record {
+
+ public static final int TAKE_PHOTO_STATE = 0;
+ public static final int READY_FOR_RECORD_STATE = 1;
+ public static final int RECORD_IN_PROGRESS_STATE = 2;
+
+ @IntDef({TAKE_PHOTO_STATE, READY_FOR_RECORD_STATE, RECORD_IN_PROGRESS_STATE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RecordState {
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/CameraManager.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/CameraManager.java
new file mode 100644
index 0000000..d909343
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/CameraManager.java
@@ -0,0 +1,57 @@
+package com.github.florent37.camerafragment.internal.manager;
+
+import android.content.Context;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+public interface CameraManager {
+
+ void initializeCameraManager(ConfigurationProvider configurationProvider, Context context);
+
+ void openCamera(CameraId cameraId, CameraOpenListener cameraOpenListener);
+
+ void closeCamera(CameraCloseListener cameraCloseListener);
+
+ void setFlashMode(@Configuration.FlashMode int flashMode);
+
+ void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener);
+
+ void startVideoRecord(File videoFile, CameraVideoListener cameraVideoListener);
+
+ Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality);
+
+ void stopVideoRecord();
+
+ void releaseCameraManager();
+
+ CameraId getCurrentCameraId();
+
+ CameraId getFaceFrontCameraId();
+
+ CameraId getFaceBackCameraId();
+
+ int getNumberOfCameras();
+
+ int getFaceFrontCameraOrientation();
+
+ int getFaceBackCameraOrientation();
+
+ boolean isVideoRecording();
+
+ CharSequence[] getVideoQualityOptions();
+
+ CharSequence[] getPhotoQualityOptions();
+
+ void setCameraId(CameraId currentCameraId);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/BaseCameraManager.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/BaseCameraManager.java
new file mode 100644
index 0000000..7d782df
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/BaseCameraManager.java
@@ -0,0 +1,148 @@
+package com.github.florent37.camerafragment.internal.manager.impl;
+
+import android.content.Context;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.util.Log;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.manager.CameraManager;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+abstract class BaseCameraManager
+ implements CameraManager, MediaRecorder.OnInfoListener {
+
+ private static final String TAG = "BaseCameraManager";
+
+ protected Context context;
+ ConfigurationProvider configurationProvider;
+
+ MediaRecorder videoRecorder;
+ boolean isVideoRecording = false;
+
+ CameraId currentCameraId = null;
+ CameraId faceFrontCameraId = null;
+ CameraId faceBackCameraId = null;
+ int numberOfCameras = 0;
+ int faceFrontCameraOrientation;
+ int faceBackCameraOrientation;
+
+ CamcorderProfile camcorderProfile;
+ Size photoSize;
+ Size videoSize;
+ Size previewSize;
+ Size windowSize;
+
+ HandlerThread backgroundThread;
+ Handler backgroundHandler;
+ Handler uiHandler = new Handler(Looper.getMainLooper());
+
+ @Override
+ public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) {
+ this.context = context;
+ this.configurationProvider = configurationProvider;
+ startBackgroundThread();
+ }
+
+ @Override
+ public void releaseCameraManager() {
+ this.context = null;
+ stopBackgroundThread();
+ }
+
+ protected abstract void prepareCameraOutputs();
+
+ protected abstract boolean prepareVideoRecorder();
+
+ protected abstract void onMaxDurationReached();
+
+ protected abstract void onMaxFileSizeReached();
+
+ protected abstract int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition);
+
+ protected abstract int getVideoOrientation(@Configuration.SensorPosition int sensorPosition);
+
+ protected void releaseVideoRecorder() {
+ try {
+ if (videoRecorder != null) {
+ videoRecorder.reset();
+ videoRecorder.release();
+ }
+ } catch (Exception ignore) {
+
+ } finally {
+ videoRecorder = null;
+ }
+ }
+
+ private void startBackgroundThread() {
+ backgroundThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+ backgroundThread.start();
+ backgroundHandler = new Handler(backgroundThread.getLooper());
+ }
+
+ private void stopBackgroundThread() {
+ if (Build.VERSION.SDK_INT > 17) {
+ backgroundThread.quitSafely();
+ } else backgroundThread.quit();
+
+ try {
+ backgroundThread.join();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "stopBackgroundThread: ", e);
+ } finally {
+ backgroundThread = null;
+ backgroundHandler = null;
+ }
+ }
+
+ @Override
+ public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
+ if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED == what) {
+ onMaxDurationReached();
+ } else if (MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED == what) {
+ onMaxFileSizeReached();
+ }
+ }
+
+ public boolean isVideoRecording() {
+ return isVideoRecording;
+ }
+
+ public CameraId getCurrentCameraId() {
+ return currentCameraId;
+ }
+
+ public CameraId getFaceFrontCameraId() {
+ return faceFrontCameraId;
+ }
+
+ public CameraId getFaceBackCameraId() {
+ return faceBackCameraId;
+ }
+
+ public int getNumberOfCameras() {
+ return numberOfCameras;
+ }
+
+ public int getFaceFrontCameraOrientation() {
+ return faceFrontCameraOrientation;
+ }
+
+ public int getFaceBackCameraOrientation() {
+ return faceBackCameraOrientation;
+ }
+
+ public void setCameraId(CameraId currentCameraId) {
+ this.currentCameraId = currentCameraId;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera1Manager.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera1Manager.java
new file mode 100644
index 0000000..e7d0843
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera1Manager.java
@@ -0,0 +1,627 @@
+package com.github.florent37.camerafragment.internal.manager.impl;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.media.CamcorderProfile;
+import android.media.ExifInterface;
+import android.media.MediaRecorder;
+import android.os.Build;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.WindowManager;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener;
+import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption;
+import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption;
+import com.github.florent37.camerafragment.internal.utils.CameraHelper;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+@SuppressWarnings("deprecation")
+public class Camera1Manager extends BaseCameraManager
+ implements SurfaceHolder.Callback, Camera.PictureCallback {
+
+ private static final String TAG = "Camera1Manager";
+
+ private Camera camera;
+ private Surface surface;
+
+ private static Camera1Manager currentInstance;
+
+ private int orientation;
+ private int displayRotation = 0;
+
+ private File outputPath;
+ private CameraVideoListener videoListener;
+ private CameraPhotoListener photoListener;
+
+ private Camera1Manager() {
+
+ }
+
+ public static Camera1Manager getInstance() {
+ if (currentInstance == null) currentInstance = new Camera1Manager();
+ return currentInstance;
+ }
+
+ @Override
+ public void openCamera(final Integer cameraId,
+ final CameraOpenListener cameraOpenListener) {
+ this.currentCameraId = cameraId;
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ camera = Camera.open(cameraId);
+ prepareCameraOutputs();
+ if (cameraOpenListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpened(cameraId, previewSize, currentInstance);
+ }
+ });
+ }
+ } catch (Exception error) {
+ Log.d(TAG, "Can't open camera: " + error.getMessage());
+ if (cameraOpenListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpenError();
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void closeCamera(final CameraCloseListener cameraCloseListener) {
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (camera != null) {
+ camera.release();
+ camera = null;
+ if (cameraCloseListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraCloseListener.onCameraClosed(currentCameraId);
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setFlashMode(@Configuration.FlashMode int flashMode) {
+ setFlashMode(camera, camera.getParameters(), flashMode);
+ }
+
+ @Override
+ public void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener) {
+ this.outputPath = photoFile;
+ this.photoListener = cameraPhotoListener;
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ setCameraPhotoQuality(camera);
+ camera.takePicture(null, null, currentInstance);
+ }
+ });
+ }
+
+ @Override
+ public void startVideoRecord(final File videoFile, CameraVideoListener cameraVideoListener) {
+ if (isVideoRecording) return;
+
+ this.outputPath = videoFile;
+ this.videoListener = cameraVideoListener;
+
+ if (videoListener != null)
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (prepareVideoRecorder()) {
+ videoRecorder.start();
+ isVideoRecording = true;
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ videoListener.onVideoRecordStarted(videoSize);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ public void stopVideoRecord() {
+ if (isVideoRecording)
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+
+ try {
+ if (videoRecorder != null) videoRecorder.stop();
+ } catch (Exception ignore) {
+ // ignore illegal state.
+ // appear in case time or file size reach limit and stop already called.
+ }
+
+ isVideoRecording = false;
+ releaseVideoRecorder();
+
+ if (videoListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ videoListener.onVideoRecordStopped(outputPath);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ public void releaseCameraManager() {
+ super.releaseCameraManager();
+ }
+
+ @Override
+ public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) {
+ super.initializeCameraManager(configurationProvider, context);
+
+ numberOfCameras = Camera.getNumberOfCameras();
+
+ for (int i = 0; i < numberOfCameras; ++i) {
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+
+ Camera.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+ faceBackCameraId = i;
+ faceBackCameraOrientation = cameraInfo.orientation;
+ } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ faceFrontCameraId = i;
+ faceFrontCameraOrientation = cameraInfo.orientation;
+ }
+ }
+ }
+
+ @Override
+ public Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality) {
+ return CameraHelper.getPictureSize(Size.fromList(camera.getParameters().getSupportedPictureSizes()), mediaQuality);
+ }
+
+ @Override
+ protected void prepareCameraOutputs() {
+ try {
+ if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO) {
+ camcorderProfile = CameraHelper.getCamcorderProfile(currentCameraId, configurationProvider.getVideoFileSize(), configurationProvider.getMinimumVideoDuration());
+ } else
+ camcorderProfile = CameraHelper.getCamcorderProfile(configurationProvider.getMediaQuality(), currentCameraId);
+
+ List previewSizes = Size.fromList(camera.getParameters().getSupportedPreviewSizes());
+ List pictureSizes = Size.fromList(camera.getParameters().getSupportedPictureSizes());
+ List videoSizes;
+ if (Build.VERSION.SDK_INT > 10)
+ videoSizes = Size.fromList(camera.getParameters().getSupportedVideoSizes());
+ else videoSizes = previewSizes;
+
+ videoSize = CameraHelper.getSizeWithClosestRatio(
+ (videoSizes == null || videoSizes.isEmpty()) ? previewSizes : videoSizes,
+ camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
+
+ photoSize = CameraHelper.getPictureSize(
+ (pictureSizes == null || pictureSizes.isEmpty()) ? previewSizes : pictureSizes,
+ configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO
+ ? Configuration.MEDIA_QUALITY_HIGHEST : configurationProvider.getMediaQuality());
+
+ if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO
+ || configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED) {
+ previewSize = CameraHelper.getSizeWithClosestRatio(previewSizes, photoSize.getWidth(), photoSize.getHeight());
+ } else {
+ previewSize = CameraHelper.getSizeWithClosestRatio(previewSizes, videoSize.getWidth(), videoSize.getHeight());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error while setup camera sizes.");
+ }
+ }
+
+ @Override
+ protected boolean prepareVideoRecorder() {
+ videoRecorder = new MediaRecorder();
+ try {
+ camera.lock();
+ camera.unlock();
+ videoRecorder.setCamera(camera);
+
+ videoRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
+ videoRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
+
+ videoRecorder.setOutputFormat(camcorderProfile.fileFormat);
+ videoRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
+ videoRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
+ videoRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
+ videoRecorder.setVideoEncoder(camcorderProfile.videoCodec);
+
+ videoRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
+ videoRecorder.setAudioChannels(camcorderProfile.audioChannels);
+ videoRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
+ videoRecorder.setAudioEncoder(camcorderProfile.audioCodec);
+
+ videoRecorder.setOutputFile(outputPath.toString());
+
+ if (configurationProvider.getVideoFileSize() > 0) {
+ videoRecorder.setMaxFileSize(configurationProvider.getVideoFileSize());
+
+ videoRecorder.setOnInfoListener(this);
+ }
+ if (configurationProvider.getVideoDuration() > 0) {
+ videoRecorder.setMaxDuration(configurationProvider.getVideoDuration());
+
+ videoRecorder.setOnInfoListener(this);
+ }
+
+ videoRecorder.setOrientationHint(getVideoOrientation(configurationProvider.getSensorPosition()));
+ videoRecorder.setPreviewDisplay(surface);
+
+ videoRecorder.prepare();
+
+ return true;
+ } catch (IllegalStateException error) {
+ Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage());
+ } catch (IOException error) {
+ Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage());
+ } catch (Throwable error) {
+ Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage());
+ }
+
+ releaseVideoRecorder();
+ return false;
+ }
+
+ @Override
+ protected void onMaxDurationReached() {
+ stopVideoRecord();
+ }
+
+ @Override
+ protected void onMaxFileSizeReached() {
+ stopVideoRecord();
+ }
+
+ @Override
+ protected void releaseVideoRecorder() {
+ super.releaseVideoRecorder();
+
+ try {
+ camera.lock(); // lock camera for later use
+ } catch (Exception ignore) {
+ }
+ }
+
+ //------------------------Implementation------------------
+
+ private void startPreview(SurfaceHolder surfaceHolder) {
+ try {
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ Camera.getCameraInfo(currentCameraId, cameraInfo);
+ int cameraRotationOffset = cameraInfo.orientation;
+
+ Camera.Parameters parameters = camera.getParameters();
+ setAutoFocus(camera, parameters);
+ setFlashMode(configurationProvider.getFlashMode());
+
+ if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO
+ || configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED)
+ turnPhotoCameraFeaturesOn(camera, parameters);
+ else if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO)
+ turnVideoCameraFeaturesOn(camera, parameters);
+
+ int rotation = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+ int degrees = 0;
+ switch (rotation) {
+ case Surface.ROTATION_0:
+ degrees = 0;
+ break; // Natural orientation
+ case Surface.ROTATION_90:
+ degrees = 90;
+ break; // Landscape left
+ case Surface.ROTATION_180:
+ degrees = 180;
+ break;// Upside down
+ case Surface.ROTATION_270:
+ degrees = 270;
+ break;// Landscape right
+ }
+
+ if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ displayRotation = (cameraRotationOffset + degrees) % 360;
+ displayRotation = (360 - displayRotation) % 360; // compensate
+ } else {
+ displayRotation = (cameraRotationOffset - degrees + 360) % 360;
+ }
+
+ this.camera.setDisplayOrientation(displayRotation);
+
+ if (Build.VERSION.SDK_INT > 13
+ && (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO
+ || configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED)) {
+// parameters.setRecordingHint(true);
+ }
+
+ if (Build.VERSION.SDK_INT > 14
+ && parameters.isVideoStabilizationSupported()
+ && (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO
+ || configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED)) {
+ parameters.setVideoStabilization(true);
+ }
+
+ parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
+ parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight());
+
+ camera.setParameters(parameters);
+ camera.setPreviewDisplay(surfaceHolder);
+ camera.startPreview();
+
+ } catch (IOException error) {
+ Log.d(TAG, "Error setting camera preview: " + error.getMessage());
+ } catch (Exception ignore) {
+ Log.d(TAG, "Error starting camera preview: " + ignore.getMessage());
+ }
+ }
+
+ private void turnPhotoCameraFeaturesOn(Camera camera, Camera.Parameters parameters) {
+ if (parameters.getSupportedFocusModes().contains(
+ Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+ }
+ camera.setParameters(parameters);
+ }
+
+ private void turnVideoCameraFeaturesOn(Camera camera, Camera.Parameters parameters) {
+ if (parameters.getSupportedFocusModes().contains(
+ Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
+ parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+ }
+ camera.setParameters(parameters);
+ }
+
+ private void setAutoFocus(Camera camera, Camera.Parameters parameters) {
+ try {
+ if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+ parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
+ camera.setParameters(parameters);
+ }
+ } catch (Exception ignore) {
+ }
+ }
+
+ private void setFlashMode(Camera camera, Camera.Parameters parameters, @Configuration.FlashMode int flashMode) {
+ try {
+ switch (flashMode) {
+ case Configuration.FLASH_MODE_AUTO:
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+ break;
+ case Configuration.FLASH_MODE_ON:
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
+ break;
+ case Configuration.FLASH_MODE_OFF:
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+ break;
+ default:
+ parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+ break;
+ }
+ camera.setParameters(parameters);
+ } catch (Exception ignore) {
+ }
+ }
+
+ private void setCameraPhotoQuality(Camera camera) {
+ Camera.Parameters parameters = camera.getParameters();
+
+ parameters.setPictureFormat(PixelFormat.JPEG);
+
+ if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_LOW) {
+ parameters.setJpegQuality(50);
+ } else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_MEDIUM) {
+ parameters.setJpegQuality(75);
+ } else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_HIGH) {
+ parameters.setJpegQuality(100);
+ } else if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_HIGHEST) {
+ parameters.setJpegQuality(100);
+ }
+ parameters.setPictureSize(photoSize.getWidth(), photoSize.getHeight());
+
+ camera.setParameters(parameters);
+ }
+
+ @Override
+ protected int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition) {
+ int rotate;
+ if (currentCameraId.equals(faceFrontCameraId)) {
+ rotate = (360 + faceFrontCameraOrientation + configurationProvider.getDegrees()) % 360;
+ } else {
+ rotate = (360 + faceBackCameraOrientation - configurationProvider.getDegrees()) % 360;
+ }
+
+ if (rotate == 0) {
+ orientation = ExifInterface.ORIENTATION_NORMAL;
+ } else if (rotate == 90) {
+ orientation = ExifInterface.ORIENTATION_ROTATE_90;
+ } else if (rotate == 180) {
+ orientation = ExifInterface.ORIENTATION_ROTATE_180;
+ } else if (rotate == 270) {
+ orientation = ExifInterface.ORIENTATION_ROTATE_270;
+ }
+
+ return orientation;
+
+ }
+
+ @Override
+ protected int getVideoOrientation(@Configuration.SensorPosition int sensorPosition) {
+ int degrees = 0;
+ switch (sensorPosition) {
+ case Configuration.SENSOR_POSITION_UP:
+ degrees = 0;
+ break; // Natural orientation
+ case Configuration.SENSOR_POSITION_LEFT:
+ degrees = 90;
+ break; // Landscape left
+ case Configuration.SENSOR_POSITION_UP_SIDE_DOWN:
+ degrees = 180;
+ break;// Upside down
+ case Configuration.SENSOR_POSITION_RIGHT:
+ degrees = 270;
+ break;// Landscape right
+ }
+
+ int rotate;
+ if (currentCameraId.equals(faceFrontCameraId)) {
+ rotate = (360 + faceFrontCameraOrientation + degrees) % 360;
+ } else {
+ rotate = (360 + faceBackCameraOrientation - degrees) % 360;
+ }
+ return rotate;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder surfaceHolder) {
+ if (surfaceHolder.getSurface() == null) {
+ return;
+ }
+
+ surface = surfaceHolder.getSurface();
+
+ try {
+ camera.stopPreview();
+ } catch (Exception ignore) {
+ }
+
+ startPreview(surfaceHolder);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
+ if (surfaceHolder.getSurface() == null) {
+ return;
+ }
+
+ surface = surfaceHolder.getSurface();
+
+ try {
+ camera.stopPreview();
+ } catch (Exception ignore) {
+ }
+
+ startPreview(surfaceHolder);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+
+ }
+
+ @Override
+ public void onPictureTaken(final byte[] bytes, Camera camera) {
+ File pictureFile = outputPath;
+ if (pictureFile == null) {
+ Log.d(TAG, "Error creating media file, check storage permissions.");
+ return;
+ }
+
+ try {
+ FileOutputStream fileOutputStream = new FileOutputStream(pictureFile);
+ fileOutputStream.write(bytes);
+ fileOutputStream.close();
+ } catch (FileNotFoundException error) {
+ Log.e(TAG, "File not found: " + error.getMessage());
+ } catch (IOException error) {
+ Log.e(TAG, "Error accessing file: " + error.getMessage());
+ } catch (Throwable error) {
+ Log.e(TAG, "Error saving file: " + error.getMessage());
+ }
+
+ try {
+ ExifInterface exif = new ExifInterface(pictureFile.getAbsolutePath());
+ exif.setAttribute(ExifInterface.TAG_ORIENTATION, "" + getPhotoOrientation(configurationProvider.getSensorPosition()));
+ exif.saveAttributes();
+
+ if (photoListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ photoListener.onPhotoTaken(bytes, outputPath);
+ }
+ });
+ }
+ } catch (Throwable error) {
+ Log.e(TAG, "Can't save exif info: " + error.getMessage());
+ }
+ }
+
+ @Override
+ public CharSequence[] getVideoQualityOptions() {
+ List videoQualities = new ArrayList<>();
+
+ if (configurationProvider.getMinimumVideoDuration() > 0)
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_AUTO, CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_AUTO, getCurrentCameraId()), configurationProvider.getMinimumVideoDuration()));
+
+ CamcorderProfile camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_HIGH, getCurrentCameraId());
+ double videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_HIGH, camcorderProfile, videoDuration));
+
+ camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_MEDIUM, getCurrentCameraId());
+ videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, camcorderProfile, videoDuration));
+
+ camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOW, getCurrentCameraId());
+ videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_LOW, camcorderProfile, videoDuration));
+
+ CharSequence[] array = new CharSequence[videoQualities.size()];
+ videoQualities.toArray(array);
+
+ return array;
+ }
+
+ @Override
+ public CharSequence[] getPhotoQualityOptions() {
+ List photoQualities = new ArrayList<>();
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGHEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGHEST)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGH, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGH)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_MEDIUM)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_LOWEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_LOWEST)));
+
+ CharSequence[] array = new CharSequence[photoQualities.size()];
+ photoQualities.toArray(array);
+
+ return array;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera2Manager.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera2Manager.java
new file mode 100644
index 0000000..28f0c71
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/impl/Camera2Manager.java
@@ -0,0 +1,844 @@
+package com.github.florent37.camerafragment.internal.manager.impl;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.Point;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.CamcorderProfile;
+import android.media.ImageReader;
+import android.media.MediaRecorder;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.WindowManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraCloseListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraOpenListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraPhotoListener;
+import com.github.florent37.camerafragment.internal.manager.listener.CameraVideoListener;
+import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption;
+import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption;
+import com.github.florent37.camerafragment.internal.utils.CameraHelper;
+import com.github.florent37.camerafragment.internal.utils.ImageSaver;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/9/16.
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public final class Camera2Manager extends BaseCameraManager
+ implements ImageReader.OnImageAvailableListener, TextureView.SurfaceTextureListener {
+
+ private final static String TAG = "Camera2Manager";
+
+ private static Camera2Manager currentInstance;
+
+ private CameraOpenListener cameraOpenListener;
+ private CameraPhotoListener cameraPhotoListener;
+ private CameraVideoListener cameraVideoListener;
+
+ private File outputPath;
+
+ @CameraPreviewState
+ private int previewState = STATE_PREVIEW;
+ private static final int STATE_PREVIEW = 0;
+ private static final int STATE_WAITING_LOCK = 1;
+ private static final int STATE_WAITING_PRE_CAPTURE = 2;
+ private static final int STATE_WAITING_NON_PRE_CAPTURE = 3;
+ private static final int STATE_PICTURE_TAKEN = 4;
+
+ @IntDef({STATE_PREVIEW, STATE_WAITING_LOCK, STATE_WAITING_PRE_CAPTURE, STATE_WAITING_NON_PRE_CAPTURE, STATE_PICTURE_TAKEN})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CameraPreviewState {
+ }
+
+ private CameraManager manager;
+ private CameraDevice cameraDevice;
+ private CaptureRequest previewRequest;
+ private CaptureRequest.Builder previewRequestBuilder;
+ private CameraCaptureSession captureSession;
+
+ private CameraCharacteristics frontCameraCharacteristics;
+ private CameraCharacteristics backCameraCharacteristics;
+ private StreamConfigurationMap frontCameraStreamConfigurationMap;
+ private StreamConfigurationMap backCameraStreamConfigurationMap;
+
+ private SurfaceTexture texture;
+
+ private Surface workingSurface;
+ private ImageReader imageReader;
+
+ private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ currentInstance.cameraDevice = cameraDevice;
+ if (cameraOpenListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (!TextUtils.isEmpty(currentCameraId) && previewSize != null && currentInstance != null)
+ cameraOpenListener.onCameraOpened(currentCameraId, previewSize, currentInstance);
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onDisconnected(CameraDevice cameraDevice) {
+ cameraDevice.close();
+ currentInstance.cameraDevice = null;
+
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpenError();
+ }
+ });
+ }
+
+ @Override
+ public void onError(CameraDevice cameraDevice, int error) {
+ cameraDevice.close();
+ currentInstance.cameraDevice = null;
+
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpenError();
+ }
+ });
+ }
+ };
+
+ private CameraCaptureSession.CaptureCallback captureCallback
+ = new CameraCaptureSession.CaptureCallback() {
+
+ @Override
+ public void onCaptureProgressed(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull CaptureResult partialResult) {
+ processCaptureResult(partialResult);
+ }
+
+ @Override
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ processCaptureResult(result);
+ }
+
+ };
+
+ private Camera2Manager() {
+ }
+
+ public static Camera2Manager getInstance() {
+ if (currentInstance == null) currentInstance = new Camera2Manager();
+ return currentInstance;
+ }
+
+ @Override
+ public void initializeCameraManager(ConfigurationProvider configurationProvider, Context context) {
+ super.initializeCameraManager(configurationProvider, context);
+
+ this.manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+ windowSize = new Size(size.x, size.y);
+
+ try {
+ String[] ids = manager.getCameraIdList();
+ numberOfCameras = ids.length;
+ for (String id : ids) {
+ CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
+
+ int orientation = characteristics.get(CameraCharacteristics.LENS_FACING);
+ if (orientation == CameraCharacteristics.LENS_FACING_FRONT) {
+ faceFrontCameraId = id;
+ faceFrontCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ frontCameraCharacteristics = characteristics;
+ } else {
+ faceBackCameraId = id;
+ faceBackCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ backCameraCharacteristics = characteristics;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error during camera init");
+ }
+ }
+
+ @Override
+ public void openCamera(String cameraId, final CameraOpenListener cameraOpenListener) {
+ this.currentCameraId = cameraId;
+ this.cameraOpenListener = cameraOpenListener;
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (context == null || configurationProvider == null) {
+ Log.e(TAG, "openCamera: ");
+ if (cameraOpenListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpenError();
+ }
+ });
+ }
+ return;
+ }
+ prepareCameraOutputs();
+ try {
+ manager.openCamera(currentCameraId, stateCallback, backgroundHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "openCamera: ", e);
+ if (cameraOpenListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraOpenListener.onCameraOpenError();
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void closeCamera(final CameraCloseListener cameraCloseListener) {
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ closeCamera();
+ if (cameraCloseListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraCloseListener.onCameraClosed(currentCameraId);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setFlashMode(@Configuration.FlashMode int flashMode) {
+ setFlashModeAndBuildPreviewRequest(flashMode);
+ }
+
+ @Override
+ public void takePhoto(File photoFile, CameraPhotoListener cameraPhotoListener) {
+ this.outputPath = photoFile;
+ this.cameraPhotoListener = cameraPhotoListener;
+
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ lockFocus();
+ }
+ });
+
+ }
+
+ @Override
+ public Size getPhotoSizeForQuality(@Configuration.MediaQuality int mediaQuality) {
+ StreamConfigurationMap map = currentCameraId.equals(faceBackCameraId) ? backCameraStreamConfigurationMap : frontCameraStreamConfigurationMap;
+ return CameraHelper.getPictureSize(Size.fromArray2(map.getOutputSizes(ImageFormat.JPEG)), mediaQuality);
+ }
+
+ @Override
+ public void startVideoRecord(File videoFile, final CameraVideoListener cameraVideoListener) {
+ if (isVideoRecording || texture == null) return;
+
+ this.outputPath = videoFile;
+ this.cameraVideoListener = cameraVideoListener;
+
+ if (cameraVideoListener != null)
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ closePreviewSession();
+ if (prepareVideoRecorder()) {
+
+ SurfaceTexture texture = currentInstance.texture;
+ texture.setDefaultBufferSize(videoSize.getWidth(), videoSize.getHeight());
+
+ try {
+ previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
+ List surfaces = new ArrayList<>();
+
+ Surface previewSurface = workingSurface;
+ surfaces.add(previewSurface);
+ previewRequestBuilder.addTarget(previewSurface);
+
+ workingSurface = videoRecorder.getSurface();
+ surfaces.add(workingSurface);
+ previewRequestBuilder.addTarget(workingSurface);
+
+ cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+ captureSession = cameraCaptureSession;
+
+ previewRequestBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
+ try {
+ captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler);
+ } catch (Exception e) {
+ }
+
+ try {
+ videoRecorder.start();
+ } catch (Exception ignore) {
+ Log.e(TAG, "videoRecorder.start(): ", ignore);
+ }
+
+ isVideoRecording = true;
+
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraVideoListener.onVideoRecordStarted(videoSize);
+ }
+ });
+ }
+
+ @Override
+ public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
+ Log.d(TAG, "onConfigureFailed");
+ }
+ }, backgroundHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "startVideoRecord: ", e);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void stopVideoRecord() {
+ if (isVideoRecording)
+ backgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ closePreviewSession();
+
+ if (videoRecorder != null) {
+ try {
+ videoRecorder.stop();
+ } catch (Exception ignore) {
+ }
+ }
+ isVideoRecording = false;
+ releaseVideoRecorder();
+
+ if (cameraVideoListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraVideoListener.onVideoRecordStopped(outputPath);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ //--------------------Internal methods------------------
+
+ private void startPreview(SurfaceTexture texture) {
+ try {
+ if (texture == null) return;
+
+ this.texture = texture;
+
+ texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
+
+ workingSurface = new Surface(texture);
+
+ previewRequestBuilder
+ = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+ previewRequestBuilder.addTarget(workingSurface);
+
+ cameraDevice.createCaptureSession(Arrays.asList(workingSurface, imageReader.getSurface()),
+ new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+ updatePreview(cameraCaptureSession);
+ }
+
+ @Override
+ public void onConfigureFailed(
+ @NonNull CameraCaptureSession cameraCaptureSession) {
+ Log.d(TAG, "Fail while starting preview: ");
+ }
+ }, null);
+ } catch (Exception e) {
+ Log.e(TAG, "Error while preparing surface for preview: ", e);
+ }
+ }
+
+ @Override
+ protected void onMaxDurationReached() {
+ stopVideoRecord();
+ }
+
+ @Override
+ protected void onMaxFileSizeReached() {
+ stopVideoRecord();
+ }
+
+ @Override
+ protected int getPhotoOrientation(@Configuration.SensorPosition int sensorPosition) {
+ return getVideoOrientation(sensorPosition);
+ }
+
+ @Override
+ protected int getVideoOrientation(@Configuration.SensorPosition int sensorPosition) {
+ int degrees = 0;
+ switch (sensorPosition) {
+ case Configuration.SENSOR_POSITION_UP:
+ degrees = 0;
+ break; // Natural orientation
+ case Configuration.SENSOR_POSITION_LEFT:
+ degrees = 90;
+ break; // Landscape left
+ case Configuration.SENSOR_POSITION_UP_SIDE_DOWN:
+ degrees = 180;
+ break;// Upside down
+ case Configuration.SENSOR_POSITION_RIGHT:
+ degrees = 270;
+ break;// Landscape right
+ case Configuration.SENSOR_POSITION_UNSPECIFIED:
+ break;
+ }
+
+ int rotate;
+ if (Objects.equals(currentCameraId, faceFrontCameraId)) {
+ rotate = (360 + faceFrontCameraOrientation + degrees) % 360;
+ } else {
+ rotate = (360 + faceBackCameraOrientation - degrees) % 360;
+ }
+ return rotate;
+ }
+
+ private void closeCamera() {
+ closePreviewSession();
+ releaseTexture();
+ closeCameraDevice();
+ closeImageReader();
+ releaseVideoRecorder();
+ }
+
+ private void releaseTexture() {
+ if (null != texture) {
+ texture.release();
+ texture = null;
+ }
+ }
+
+ private void closeImageReader() {
+ if (null != imageReader) {
+ imageReader.close();
+ imageReader = null;
+ }
+ }
+
+ private void closeCameraDevice() {
+ if (null != cameraDevice) {
+ cameraDevice.close();
+ cameraDevice = null;
+ }
+ }
+
+ @Override
+ protected void prepareCameraOutputs() {
+ try {
+ CameraCharacteristics characteristics = currentCameraId.equals(faceBackCameraId) ? backCameraCharacteristics : frontCameraCharacteristics;
+
+ if (currentCameraId.equals(faceFrontCameraId) && frontCameraStreamConfigurationMap == null)
+ frontCameraStreamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ else if (currentCameraId.equals(faceBackCameraId) && backCameraStreamConfigurationMap == null)
+ backCameraStreamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+ StreamConfigurationMap map = currentCameraId.equals(faceBackCameraId) ? backCameraStreamConfigurationMap : frontCameraStreamConfigurationMap;
+ if (configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO) {
+ camcorderProfile = CameraHelper.getCamcorderProfile(currentCameraId, configurationProvider.getVideoFileSize(), configurationProvider.getMinimumVideoDuration());
+ } else
+ camcorderProfile = CameraHelper.getCamcorderProfile(configurationProvider.getMediaQuality(), currentCameraId);
+
+ videoSize = CameraHelper.chooseOptimalSize(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)),
+ windowSize.getWidth(), windowSize.getHeight(), new Size(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight));
+
+ if (videoSize == null || videoSize.getWidth() > camcorderProfile.videoFrameWidth
+ || videoSize.getHeight() > camcorderProfile.videoFrameHeight)
+ videoSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)), camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
+ else if (videoSize == null || videoSize.getWidth() > camcorderProfile.videoFrameWidth
+ || videoSize.getHeight() > camcorderProfile.videoFrameHeight)
+ videoSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(MediaRecorder.class)), camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);
+
+ photoSize = CameraHelper.getPictureSize(Size.fromArray2(map.getOutputSizes(ImageFormat.JPEG)),
+ configurationProvider.getMediaQuality() == Configuration.MEDIA_QUALITY_AUTO
+ ? Configuration.MEDIA_QUALITY_HIGHEST : configurationProvider.getMediaQuality());
+
+ imageReader = ImageReader.newInstance(photoSize.getWidth(), photoSize.getHeight(),
+ ImageFormat.JPEG, 2);
+ imageReader.setOnImageAvailableListener(this, backgroundHandler);
+
+ if (configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_PHOTO
+ || configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_UNSPECIFIED) {
+
+ if (windowSize.getHeight() * windowSize.getWidth() > photoSize.getWidth() * photoSize.getHeight()) {
+ previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), photoSize.getWidth(), photoSize.getHeight());
+ } else {
+ previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight());
+ }
+
+ if (previewSize == null)
+ previewSize = CameraHelper.chooseOptimalSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight(), photoSize);
+
+ } else {
+ if (windowSize.getHeight() * windowSize.getWidth() > videoSize.getWidth() * videoSize.getHeight()) {
+ previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), videoSize.getWidth(), videoSize.getHeight());
+ } else {
+ previewSize = CameraHelper.getOptimalPreviewSize(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), windowSize.getWidth(), windowSize.getHeight());
+ }
+
+ if (previewSize == null)
+ previewSize = CameraHelper.getSizeWithClosestRatio(Size.fromArray2(map.getOutputSizes(SurfaceTexture.class)), videoSize.getWidth(), videoSize.getHeight());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error while setup camera sizes.", e);
+ }
+ }
+
+ @Override
+ protected boolean prepareVideoRecorder() {
+ videoRecorder = new MediaRecorder();
+ try {
+ videoRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ videoRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+
+ videoRecorder.setOutputFormat(camcorderProfile.fileFormat);
+ videoRecorder.setVideoFrameRate(camcorderProfile.videoFrameRate);
+ videoRecorder.setVideoSize(videoSize.getWidth(), videoSize.getHeight());
+ videoRecorder.setVideoEncodingBitRate(camcorderProfile.videoBitRate);
+ videoRecorder.setVideoEncoder(camcorderProfile.videoCodec);
+
+ videoRecorder.setAudioEncodingBitRate(camcorderProfile.audioBitRate);
+ videoRecorder.setAudioChannels(camcorderProfile.audioChannels);
+ videoRecorder.setAudioSamplingRate(camcorderProfile.audioSampleRate);
+ videoRecorder.setAudioEncoder(camcorderProfile.audioCodec);
+
+ File outputFile = outputPath;
+ String outputFilePath = outputFile.toString();
+ videoRecorder.setOutputFile(outputFilePath);
+
+ if (configurationProvider.getVideoFileSize() > 0) {
+ videoRecorder.setMaxFileSize(configurationProvider.getVideoFileSize());
+ videoRecorder.setOnInfoListener(this);
+ }
+ if (configurationProvider.getVideoDuration() > 0) {
+ videoRecorder.setMaxDuration(configurationProvider.getVideoDuration());
+ videoRecorder.setOnInfoListener(this);
+ }
+ videoRecorder.setOrientationHint(getVideoOrientation(configurationProvider.getSensorPosition()));
+
+ videoRecorder.prepare();
+
+ return true;
+ } catch (IllegalStateException error) {
+ Log.e(TAG, "IllegalStateException preparing MediaRecorder: " + error.getMessage());
+ } catch (IOException error) {
+ Log.e(TAG, "IOException preparing MediaRecorder: " + error.getMessage());
+ } catch (Throwable error) {
+ Log.e(TAG, "Error during preparing MediaRecorder: " + error.getMessage());
+ }
+
+ releaseVideoRecorder();
+ return false;
+ }
+
+ private void updatePreview(CameraCaptureSession cameraCaptureSession) {
+ if (null == cameraDevice) {
+ return;
+ }
+ captureSession = cameraCaptureSession;
+
+ setFlashModeAndBuildPreviewRequest(configurationProvider.getFlashMode());
+ }
+
+ private void closePreviewSession() {
+ if (captureSession != null) {
+ captureSession.close();
+ try {
+ captureSession.abortCaptures();
+ } catch (Exception ignore) {
+ } finally {
+ captureSession = null;
+ }
+ }
+ }
+
+ private void lockFocus() {
+ try {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
+
+ previewState = STATE_WAITING_LOCK;
+ captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
+ } catch (Exception ignore) {
+ }
+ }
+
+ private void processCaptureResult(CaptureResult result) {
+ switch (previewState) {
+ case STATE_PREVIEW: {
+ break;
+ }
+ case STATE_WAITING_LOCK: {
+ Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (afState == null) {
+ captureStillPicture();
+ } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState
+ || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState
+ || CaptureResult.CONTROL_AF_STATE_INACTIVE == afState
+ || CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN == afState) {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null ||
+ aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ previewState = STATE_PICTURE_TAKEN;
+ captureStillPicture();
+ } else {
+ runPreCaptureSequence();
+ }
+ }
+ break;
+ }
+ case STATE_WAITING_PRE_CAPTURE: {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null ||
+ aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
+ aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
+ previewState = STATE_WAITING_NON_PRE_CAPTURE;
+ }
+ break;
+ }
+ case STATE_WAITING_NON_PRE_CAPTURE: {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+ previewState = STATE_PICTURE_TAKEN;
+ captureStillPicture();
+ }
+ break;
+ }
+ case STATE_PICTURE_TAKEN:
+ break;
+ }
+ }
+
+ private void runPreCaptureSequence() {
+ try {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ previewState = STATE_WAITING_PRE_CAPTURE;
+ captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
+ } catch (CameraAccessException e) {
+ }
+ }
+
+ private void captureStillPicture() {
+ try {
+ if (null == cameraDevice) {
+ return;
+ }
+ final CaptureRequest.Builder captureBuilder =
+ cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ captureBuilder.addTarget(imageReader.getSurface());
+
+ captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+ captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getPhotoOrientation(configurationProvider.getSensorPosition()));
+
+ CameraCaptureSession.CaptureCallback CaptureCallback = new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+ @NonNull CaptureRequest request,
+ @NonNull TotalCaptureResult result) {
+ Log.d(TAG, "onCaptureCompleted: ");
+ }
+ };
+
+ captureSession.stopRepeating();
+ captureSession.capture(captureBuilder.build(), CaptureCallback, null);
+
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Error during capturing picture");
+ }
+ }
+
+ private void unlockFocus() {
+ try {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+ captureSession.capture(previewRequestBuilder.build(), captureCallback, backgroundHandler);
+ previewState = STATE_PREVIEW;
+ captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "Error during focus unlocking");
+ }
+ }
+
+ private void setFlashModeAndBuildPreviewRequest(@Configuration.FlashMode int flashMode) {
+ try {
+
+ switch (flashMode) {
+ case Configuration.FLASH_MODE_AUTO:
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
+ break;
+ case Configuration.FLASH_MODE_ON:
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
+ break;
+ case Configuration.FLASH_MODE_OFF:
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
+ previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
+ break;
+ default:
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+ previewRequestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
+ break;
+ }
+
+ previewRequest = previewRequestBuilder.build();
+
+ try {
+ captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
+ } catch (Exception e) {
+ Log.e(TAG, "Error updating preview: ", e);
+ }
+ } catch (Exception ignore) {
+ Log.e(TAG, "Error setting flash: ", ignore);
+ }
+ }
+
+ @Override
+ public void onImageAvailable(ImageReader imageReader) {
+ File outputFile = outputPath;
+ backgroundHandler.post(new ImageSaver(imageReader.acquireNextImage(), outputFile, new ImageSaver.ImageSaverCallback() {
+ @Override
+ public void onSuccessFinish(final byte[] bytes) {
+ Log.d(TAG, "onPhotoSuccessFinish: ");
+ if (cameraPhotoListener != null) {
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraPhotoListener.onPhotoTaken(bytes, outputPath);
+ }
+ });
+ }
+ unlockFocus();
+ }
+
+ @Override
+ public void onError() {
+ Log.d(TAG, "onPhotoError: ");
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ cameraPhotoListener.onPhotoTakeError();
+ }
+ });
+ }
+ }));
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+ if (surfaceTexture != null) startPreview(surfaceTexture);
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
+ if (surfaceTexture != null) startPreview(surfaceTexture);
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+ }
+
+ @Override
+ public CharSequence[] getVideoQualityOptions() {
+ List videoQualities = new ArrayList<>();
+
+ if (configurationProvider.getMinimumVideoDuration() > 0)
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_AUTO, CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_AUTO, getCurrentCameraId()), configurationProvider.getMinimumVideoDuration()));
+
+
+ CamcorderProfile camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_HIGH, currentCameraId);
+ double videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_HIGH, camcorderProfile, videoDuration));
+
+ camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_MEDIUM, currentCameraId);
+ videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, camcorderProfile, videoDuration));
+
+ camcorderProfile = CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOW, currentCameraId);
+ videoDuration = CameraHelper.calculateApproximateVideoDuration(camcorderProfile, configurationProvider.getVideoFileSize());
+ videoQualities.add(new VideoQualityOption(Configuration.MEDIA_QUALITY_LOW, camcorderProfile, videoDuration));
+
+ CharSequence[] array = new CharSequence[videoQualities.size()];
+ videoQualities.toArray(array);
+
+ return array;
+ }
+
+ @Override
+ public CharSequence[] getPhotoQualityOptions() {
+ List photoQualities = new ArrayList<>();
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGHEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGHEST)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_HIGH, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_HIGH)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_MEDIUM, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_MEDIUM)));
+ photoQualities.add(new PhotoQualityOption(Configuration.MEDIA_QUALITY_LOWEST, getPhotoSizeForQuality(Configuration.MEDIA_QUALITY_LOWEST)));
+
+ CharSequence[] array = new CharSequence[photoQualities.size()];
+ photoQualities.toArray(array);
+
+ return array;
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraCloseListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraCloseListener.java
new file mode 100644
index 0000000..55d049e
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraCloseListener.java
@@ -0,0 +1,8 @@
+package com.github.florent37.camerafragment.internal.manager.listener;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+public interface CameraCloseListener {
+ void onCameraClosed(CameraId closedCameraId);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraOpenListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraOpenListener.java
new file mode 100644
index 0000000..bfb4c79
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraOpenListener.java
@@ -0,0 +1,12 @@
+package com.github.florent37.camerafragment.internal.manager.listener;
+
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+public interface CameraOpenListener {
+ void onCameraOpened(CameraId openedCameraId, Size previewSize, SurfaceListener surfaceListener);
+
+ void onCameraOpenError();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraPhotoListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraPhotoListener.java
new file mode 100644
index 0000000..2229836
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraPhotoListener.java
@@ -0,0 +1,12 @@
+package com.github.florent37.camerafragment.internal.manager.listener;
+
+import java.io.File;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+public interface CameraPhotoListener {
+ void onPhotoTaken(byte[] bytes, File photoFile);
+
+ void onPhotoTakeError();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraVideoListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraVideoListener.java
new file mode 100644
index 0000000..6cc2013
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/manager/listener/CameraVideoListener.java
@@ -0,0 +1,16 @@
+package com.github.florent37.camerafragment.internal.manager.listener;
+
+import java.io.File;
+
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 8/14/16.
+ */
+public interface CameraVideoListener {
+ void onVideoRecordStarted(Size videoSize);
+
+ void onVideoRecordStopped(File videoFile);
+
+ void onVideoRecordError();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/CountdownTask.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/CountdownTask.java
new file mode 100644
index 0000000..a76bd2f
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/CountdownTask.java
@@ -0,0 +1,64 @@
+package com.github.florent37.camerafragment.internal.timer;
+
+import android.graphics.Color;
+
+import java.util.concurrent.TimeUnit;
+
+import com.github.florent37.camerafragment.internal.utils.DateTimeUtils;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public class CountdownTask extends TimerTaskBase implements Runnable {
+
+ private int maxDurationMilliseconds = 0;
+
+ public CountdownTask(Callback callback, int maxDurationMilliseconds) {
+ super(callback);
+ this.maxDurationMilliseconds = maxDurationMilliseconds;
+ }
+
+ @Override
+ public void run() {
+
+ recordingTimeSeconds--;
+
+ int millis = (int) recordingTimeSeconds * 1000;
+
+ if (callback != null) {
+ callback.setText(
+ String.format("%02d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(millis),
+ TimeUnit.MILLISECONDS.toSeconds(millis) -
+ TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
+ ));
+ }
+
+ if (alive && recordingTimeSeconds > 0) handler.postDelayed(this, DateTimeUtils.SECOND);
+ }
+
+ @Override
+ public void stop() {
+ if (callback != null) {
+ callback.setTextVisible(false);
+ }
+ alive = false;
+ }
+
+ @Override
+ public void start() {
+ alive = true;
+ recordingTimeSeconds = maxDurationMilliseconds / 1000;
+ if (callback != null) {
+ callback.setText(
+ String.format("%02d:%02d",
+ TimeUnit.MILLISECONDS.toMinutes(maxDurationMilliseconds),
+ TimeUnit.MILLISECONDS.toSeconds(maxDurationMilliseconds) -
+ TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(maxDurationMilliseconds))
+ ));
+ callback.setTextVisible(true);
+ }
+ handler.postDelayed(this, DateTimeUtils.SECOND);
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTask.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTask.java
new file mode 100644
index 0000000..e51db13
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTask.java
@@ -0,0 +1,48 @@
+package com.github.florent37.camerafragment.internal.timer;
+
+import com.github.florent37.camerafragment.internal.utils.DateTimeUtils;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public class TimerTask extends TimerTaskBase implements Runnable {
+
+ public TimerTask(TimerTaskBase.Callback callback) {
+ super(callback);
+ }
+
+ @Override
+ public void run() {
+ recordingTimeSeconds++;
+
+ if (recordingTimeSeconds == 60) {
+ recordingTimeSeconds = 0;
+ recordingTimeMinutes++;
+ }
+ if(callback != null) {
+ callback.setText(
+ String.format("%02d:%02d", recordingTimeMinutes, recordingTimeSeconds));
+ }
+ if (alive) handler.postDelayed(this, DateTimeUtils.SECOND);
+ }
+
+ public void start() {
+ alive = true;
+ recordingTimeMinutes = 0;
+ recordingTimeSeconds = 0;
+ if(callback != null) {
+ callback.setText(
+ String.format("%02d:%02d", recordingTimeMinutes, recordingTimeSeconds));
+ callback.setTextVisible(true);
+ }
+ handler.postDelayed(this, DateTimeUtils.SECOND);
+ }
+
+ public void stop() {
+ if(callback != null){
+ callback.setTextVisible(false);
+ }
+ alive = false;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTaskBase.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTaskBase.java
new file mode 100644
index 0000000..57d9a51
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/timer/TimerTaskBase.java
@@ -0,0 +1,31 @@
+package com.github.florent37.camerafragment.internal.timer;
+
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public abstract class TimerTaskBase {
+
+ public interface Callback {
+ void setText(String text);
+ void setTextVisible(boolean visible);
+ }
+
+ protected Handler handler = new Handler(Looper.getMainLooper());
+ protected boolean alive = false;
+ protected long recordingTimeSeconds = 0;
+ protected long recordingTimeMinutes = 0;
+
+ protected Callback callback;
+
+ protected TimerTaskBase(Callback callback) {
+ this.callback = callback;
+ }
+
+ public abstract void stop();
+
+ public abstract void start();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/BaseAnncaFragment.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/BaseAnncaFragment.java
new file mode 100644
index 0000000..54d2eed
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/BaseAnncaFragment.java
@@ -0,0 +1,703 @@
+package com.github.florent37.camerafragment.internal.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.MediaActionSound;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.github.florent37.camerafragment.R;
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.configuration.ConfigurationProvider;
+import com.github.florent37.camerafragment.configuration.ConfigurationProviderImpl;
+import com.github.florent37.camerafragment.internal.controller.CameraController;
+import com.github.florent37.camerafragment.internal.controller.impl.Camera1Controller;
+import com.github.florent37.camerafragment.internal.controller.impl.Camera2Controller;
+import com.github.florent37.camerafragment.internal.controller.view.CameraView;
+import com.github.florent37.camerafragment.internal.enums.Camera;
+import com.github.florent37.camerafragment.internal.enums.Flash;
+import com.github.florent37.camerafragment.internal.enums.MediaAction;
+import com.github.florent37.camerafragment.internal.enums.Record;
+import com.github.florent37.camerafragment.internal.timer.CountdownTask;
+import com.github.florent37.camerafragment.internal.timer.TimerTask;
+import com.github.florent37.camerafragment.internal.timer.TimerTaskBase;
+import com.github.florent37.camerafragment.internal.ui.model.PhotoQualityOption;
+import com.github.florent37.camerafragment.internal.ui.model.VideoQualityOption;
+import com.github.florent37.camerafragment.internal.ui.view.AspectFrameLayout;
+import com.github.florent37.camerafragment.internal.utils.CameraHelper;
+import com.github.florent37.camerafragment.internal.utils.Size;
+import com.github.florent37.camerafragment.internal.utils.Utils;
+import com.github.florent37.camerafragment.listeners.CameraFragmentControlsListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentResultListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentStateListener;
+import com.github.florent37.camerafragment.listeners.CameraFragmentVideoRecordTextListener;
+
+import java.io.File;
+
+/**
+ * Created by memfis on 12/1/16.
+ * Updated by Florent37
+ */
+
+public abstract class BaseAnncaFragment extends Fragment {
+
+ public static final String ARG_CONFIGURATION = "configuration";
+ public static final int MIN_VERSION_ICECREAM = Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
+
+ protected CharSequence[] videoQualities;
+ protected CharSequence[] photoQualities;
+ protected AspectFrameLayout previewContainer;
+
+ protected ConfigurationProvider configurationProvider;
+ @Configuration.MediaQuality
+ protected int newQuality = -1;
+ private Configuration configuration;
+ private SensorManager sensorManager = null;
+ private CameraController cameraController;
+ private AlertDialog settingsDialog;
+ private CameraFragmentControlsListener cameraFragmentControlsListener;
+ private CameraFragmentVideoRecordTextListener cameraFragmentVideoRecordTextListener;
+ private CameraFragmentStateListener cameraFragmentStateListener;
+ @Flash.FlashMode
+ private int currentFlashMode = Flash.FLASH_AUTO;
+
+ @Camera.CameraType
+ private int currentCameraType = Camera.CAMERA_TYPE_REAR;
+
+ @MediaAction.MediaActionState
+ private int currentMediaActionState = MediaAction.ACTION_PHOTO;
+
+ @Record.RecordState
+ private int currentRecordState = Record.TAKE_PHOTO_STATE;
+ private String mediaFilePath;
+ private FileObserver fileObserver;
+ private long maxVideoFileSize = 0;
+ private TimerTaskBase countDownTimer;
+ private SensorEventListener sensorEventListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent sensorEvent) {
+ synchronized (this) {
+ if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+ if (sensorEvent.values[0] < 4 && sensorEvent.values[0] > -4) {
+ if (sensorEvent.values[1] > 0) {
+ // UP
+ configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_UP);
+ configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 0 : 90);
+ } else if (sensorEvent.values[1] < 0) {
+ // UP SIDE DOWN
+ configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_UP_SIDE_DOWN);
+ configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 180 : 270);
+ }
+ } else if (sensorEvent.values[1] < 4 && sensorEvent.values[1] > -4) {
+ if (sensorEvent.values[0] > 0) {
+ // LEFT
+ configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_LEFT);
+ configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 90 : 180);
+ } else if (sensorEvent.values[0] < 0) {
+ // RIGHT
+ configurationProvider.setSensorPosition(Configuration.SENSOR_POSITION_RIGHT);
+ configurationProvider.setDegrees(configurationProvider.getDeviceDefaultOrientation() == Configuration.ORIENTATION_PORTRAIT ? 270 : 0);
+ }
+ }
+ onScreenRotation(configurationProvider.getDegrees());
+ }
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int i) {
+
+ }
+ };
+ private CameraFragmentResultListener cameraFragmentResultListener;
+
+ protected static Fragment newInstance(Fragment fragment, Configuration configuration) {
+ Bundle args = new Bundle();
+ args.putSerializable(ARG_CONFIGURATION, configuration);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View decorView = ((Activity) container.getContext()).getWindow().getDecorView();
+ if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) {
+ int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ decorView.setSystemUiVisibility(uiOptions);
+ }
+
+ return inflater.inflate(R.layout.generic_camera_layout, container, false);
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Bundle arguments = getArguments();
+ if (arguments != null) {
+ configuration = (Configuration) arguments.getSerializable(ARG_CONFIGURATION);
+ }
+ this.configurationProvider = new ConfigurationProviderImpl();
+ this.configurationProvider.setupWithAnnaConfiguration(configuration);
+
+ //onProcessBundle
+ currentMediaActionState = configurationProvider.getMediaAction() == Configuration.MEDIA_ACTION_VIDEO ?
+ MediaAction.ACTION_VIDEO : MediaAction.ACTION_PHOTO;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ previewContainer = (AspectFrameLayout) view.findViewById(R.id.previewContainer);
+
+ final CameraView cameraView = new CameraView() {
+
+ @Override
+ public void updateCameraPreview(Size size, View cameraPreview) {
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.unLockControls();
+ cameraFragmentControlsListener.allowRecord(true);
+ }
+
+ setCameraPreview(cameraPreview, size);
+ }
+
+ @Override
+ public void updateUiForMediaAction(@Configuration.MediaAction int mediaAction) {
+
+ }
+
+ @Override
+ public void updateCameraSwitcher(int numberOfCameras) {
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.allowCameraSwitching(numberOfCameras > 1);
+ }
+ }
+
+ @Override
+ public void onPhotoTaken(byte[] bytes) {
+ if (cameraFragmentResultListener != null) {
+ final String filePath = getCameraController().getOutputFile().toString();
+ cameraFragmentResultListener.onPhotoTaken(bytes, filePath);
+ }
+ }
+
+ @Override
+ public void onVideoRecordStart(int width, int height) {
+ final File outputFile = getCameraController().getOutputFile();
+ onStartVideoRecord(outputFile);
+ }
+
+ @Override
+ public void onVideoRecordStop() {
+ BaseAnncaFragment.this.onStopVideoRecord();
+ }
+
+ @Override
+ public void releaseCameraPreview() {
+ clearCameraPreview();
+ }
+ };
+
+ if (CameraHelper.hasCamera2(getContext())) {
+ cameraController = new Camera2Controller(getActivity(), cameraView, configurationProvider);
+ } else {
+ cameraController = new Camera1Controller(getActivity(), cameraView, configurationProvider);
+ }
+ cameraController.onCreate(savedInstanceState);
+
+ sensorManager = (SensorManager) getContext().getSystemService(Activity.SENSOR_SERVICE);
+ final int defaultOrientation = Utils.getDeviceDefaultOrientation(getContext());
+ switch (defaultOrientation) {
+ case android.content.res.Configuration.ORIENTATION_LANDSCAPE:
+ configurationProvider.setDeviceDefaultOrientation(Configuration.ORIENTATION_LANDSCAPE);
+ break;
+ default:
+ configurationProvider.setDeviceDefaultOrientation(Configuration.ORIENTATION_PORTRAIT);
+ break;
+ }
+
+ switch (configurationProvider.getFlashMode()) {
+ case Configuration.FLASH_MODE_AUTO:
+ setFlashMode(Flash.FLASH_AUTO);
+ break;
+ case Configuration.FLASH_MODE_ON:
+ setFlashMode(Flash.FLASH_ON);
+ break;
+ case Configuration.FLASH_MODE_OFF:
+ setFlashMode(Flash.FLASH_OFF);
+ break;
+ }
+
+ if (cameraFragmentControlsListener != null) {
+ setMaxVideoDuration(configurationProvider.getVideoDuration());
+ setMaxVideoFileSize(configurationProvider.getVideoFileSize());
+ }
+
+ }
+
+ public void takePhotoOrCaptureVideo() {
+ switch (currentMediaActionState) {
+ case MediaAction.ACTION_PHOTO:
+ takePhoto();
+ break;
+ case MediaAction.ACTION_VIDEO:
+ switch (currentRecordState) {
+ case Record.RECORD_IN_PROGRESS_STATE:
+ stopRecording();
+ break;
+ default:
+ startRecording();
+ break;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ cameraController.onResume();
+ sensorManager.registerListener(sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
+
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.lockControls();
+ cameraFragmentControlsListener.allowRecord(false);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ cameraController.onPause();
+ sensorManager.unregisterListener(sensorEventListener);
+
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.lockControls();
+ cameraFragmentControlsListener.allowRecord(false);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ cameraController.onDestroy();
+ }
+
+ public void setMaxVideoFileSize(long maxVideoFileSize) {
+ this.maxVideoFileSize = maxVideoFileSize;
+ }
+
+ public void setMaxVideoDuration(int maxVideoDurationInMillis) {
+ final TimerTaskBase.Callback callback = new TimerTaskBase.Callback() {
+ @Override
+ public void setText(String text) {
+ if (cameraFragmentVideoRecordTextListener != null) {
+ cameraFragmentVideoRecordTextListener.setRecordDurationText(text);
+ }
+ }
+
+ @Override
+ public void setTextVisible(boolean visible) {
+ if (cameraFragmentVideoRecordTextListener != null) {
+ cameraFragmentVideoRecordTextListener.setRecordDurationTextVisible(visible);
+ }
+ }
+ };
+
+ if (maxVideoDurationInMillis > 0) {
+ this.countDownTimer = new CountdownTask(callback, maxVideoDurationInMillis);
+ } else {
+ this.countDownTimer = new TimerTask(callback);
+ }
+ }
+
+ public void openSettingDialog() {
+ final Context context = getContext();
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+
+ if (currentMediaActionState == MediaAction.ACTION_VIDEO) {
+ builder.setSingleChoiceItems(videoQualities, getVideoOptionCheckedIndex(), getVideoOptionSelectedListener());
+ if (configurationProvider.getVideoFileSize() > 0)
+ builder.setTitle(String.format(getString(R.string.settings_video_quality_title),
+ "(Max " + String.valueOf(configurationProvider.getVideoFileSize() / (1024 * 1024) + " MB)")));
+ else
+ builder.setTitle(String.format(getString(R.string.settings_video_quality_title), ""));
+ } else {
+ builder.setSingleChoiceItems(photoQualities, getPhotoOptionCheckedIndex(), getPhotoOptionSelectedListener());
+ builder.setTitle(R.string.settings_photo_quality_title);
+ }
+
+ builder.setPositiveButton(R.string.ok_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ if (newQuality > 0 && newQuality != configurationProvider.getMediaQuality()) {
+ configurationProvider.setMediaQuality(newQuality);
+ dialogInterface.dismiss();
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.lockControls();
+ }
+ getCameraController().switchQuality();
+ }
+ }
+ });
+ builder.setNegativeButton(R.string.cancel_label, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ dialogInterface.dismiss();
+ }
+ });
+ settingsDialog = builder.create();
+ settingsDialog.show();
+ WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.copyFrom(settingsDialog.getWindow().getAttributes());
+ layoutParams.width = Utils.convertDipToPixels(context, 350);
+ layoutParams.height = Utils.convertDipToPixels(context, 350);
+ settingsDialog.getWindow().setAttributes(layoutParams);
+ }
+
+ public void switchCameraType() {
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.lockControls();
+ cameraFragmentControlsListener.allowRecord(false);
+ }
+
+ int cameraFace = Configuration.CAMERA_FACE_REAR;
+ switch (currentCameraType) {
+ case Camera.CAMERA_TYPE_FRONT:
+ currentCameraType = Camera.CAMERA_TYPE_REAR;
+ cameraFace = Configuration.CAMERA_FACE_REAR;
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onCurrentCameraBack();
+ }
+ break;
+ case Camera.CAMERA_TYPE_REAR:
+ currentCameraType = Camera.CAMERA_TYPE_FRONT;
+ cameraFace = Configuration.CAMERA_FACE_FRONT;
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onCurrentCameraFront();
+ }
+ break;
+ }
+
+ getCameraController().switchCamera(cameraFace);
+ }
+
+ public void switchAction() {
+ switch (currentMediaActionState) {
+ case MediaAction.ACTION_PHOTO:
+ currentMediaActionState = MediaAction.ACTION_VIDEO;
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onCameraSetupForVideo();
+ }
+ break;
+ case MediaAction.ACTION_VIDEO:
+ currentMediaActionState = MediaAction.ACTION_PHOTO;
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onCameraSetupForPhoto();
+ }
+ break;
+ }
+ }
+
+ public void toggleFlashMode() {
+ switch (currentFlashMode) {
+ case Flash.FLASH_AUTO:
+ currentFlashMode = Flash.FLASH_OFF;
+ break;
+ case Flash.FLASH_OFF:
+ currentFlashMode = Flash.FLASH_ON;
+ break;
+ case Flash.FLASH_ON:
+ currentFlashMode = Flash.FLASH_AUTO;
+ break;
+ }
+ onFlashModeChanged();
+ }
+
+ private void onFlashModeChanged() {
+ switch (currentFlashMode) {
+ case Flash.FLASH_AUTO:
+ if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashAuto();
+ configurationProvider.setFlashMode(Configuration.FLASH_MODE_AUTO);
+ getCameraController().setFlashMode(Configuration.FLASH_MODE_AUTO);
+ break;
+ case Flash.FLASH_ON:
+ if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashOn();
+ configurationProvider.setFlashMode(Configuration.FLASH_MODE_ON);
+ getCameraController().setFlashMode(Configuration.FLASH_MODE_ON);
+ break;
+ case Flash.FLASH_OFF:
+ if (cameraFragmentStateListener != null) cameraFragmentStateListener.onFlashOff();
+ configurationProvider.setFlashMode(Configuration.FLASH_MODE_OFF);
+ getCameraController().setFlashMode(Configuration.FLASH_MODE_OFF);
+ break;
+ }
+ }
+
+ protected void onScreenRotation(int degrees) {
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.shouldRotateControls(degrees);
+ }
+ rotateSettingsDialog(degrees);
+ }
+
+ @Record.RecordState
+ public int getRecordState() {
+ return currentRecordState;
+ }
+
+ public void setRecordState(@Record.RecordState int recordState) {
+ this.currentRecordState = recordState;
+ }
+
+ //@Override
+ //public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // if (resultCode == Activity.RESULT_OK) {
+ // if (requestCode == REQUEST_PREVIEW_CODE) {
+ // final FragmentActivity activity = getActivity();
+ // if (activity != null) {
+ // if (PreviewActivity.isResultConfirm(data)) {
+ // Intent resultIntent = new Intent();
+ // resultIntent.putExtra(Configuration.Arguments.FILE_PATH,
+ // PreviewActivity.getMediaFilePatch(data));
+ // activity.setResult(Activity.RESULT_OK, resultIntent);
+ // activity.finish();
+ // } else if (PreviewActivity.isResultCancel(data)) {
+ // activity.setResult(Activity.RESULT_CANCELED);
+ // activity.finish();
+ // } else if (PreviewActivity.isResultRetake(data)) {
+ // //ignore, just proceed the camera
+ // }
+ // }
+ // }
+ // }
+ //}
+
+ public void setFlashMode(@Flash.FlashMode int mode) {
+ this.currentFlashMode = mode;
+ onFlashModeChanged();
+ }
+
+ private void rotateSettingsDialog(int degrees) {
+ if (settingsDialog != null && settingsDialog.isShowing() && Build.VERSION.SDK_INT > 10) {
+ ViewGroup dialogView = (ViewGroup) settingsDialog.getWindow().getDecorView();
+ for (int i = 0; i < dialogView.getChildCount(); i++) {
+ dialogView.getChildAt(i).setRotation(degrees);
+ }
+ }
+ }
+
+ protected int getVideoOptionCheckedIndex() {
+ int checkedIndex = -1;
+
+ final int mediaQuality = configurationProvider.getMediaQuality();
+ final int passedMediaQuality = configurationProvider.getPassedMediaQuality();
+
+ if (mediaQuality == Configuration.MEDIA_QUALITY_AUTO) checkedIndex = 0;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) checkedIndex = 1;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) checkedIndex = 2;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) checkedIndex = 3;
+
+ if (passedMediaQuality != Configuration.MEDIA_QUALITY_AUTO) checkedIndex--;
+
+ return checkedIndex;
+ }
+
+ protected int getPhotoOptionCheckedIndex() {
+ int checkedIndex = -1;
+
+ final int mediaQuality = configurationProvider.getMediaQuality();
+
+ if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) checkedIndex = 0;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) checkedIndex = 1;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) checkedIndex = 2;
+ else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) checkedIndex = 3;
+ return checkedIndex;
+ }
+
+ protected DialogInterface.OnClickListener getVideoOptionSelectedListener() {
+ return new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int index) {
+ newQuality = ((VideoQualityOption) videoQualities[index]).getMediaQuality();
+ }
+ };
+ }
+
+ protected DialogInterface.OnClickListener getPhotoOptionSelectedListener() {
+ return new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int index) {
+ newQuality = ((PhotoQualityOption) photoQualities[index]).getMediaQuality();
+ }
+ };
+ }
+
+ public void takePhoto() {
+ if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) {
+ new MediaActionSound().play(MediaActionSound.SHUTTER_CLICK);
+ }
+ setRecordState(Record.TAKE_PHOTO_STATE);
+ getCameraController().takePhoto();
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onRecordStatePhoto();
+ }
+ }
+
+ public void startRecording() {
+ if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) {
+ new MediaActionSound().play(MediaActionSound.START_VIDEO_RECORDING);
+ }
+
+ setRecordState(Record.RECORD_IN_PROGRESS_STATE);
+ getCameraController().startVideoRecord();
+
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onRecordStateVideoInProgress();
+ }
+ }
+
+ public void stopRecording() {
+ if (Build.VERSION.SDK_INT > MIN_VERSION_ICECREAM) {
+ new MediaActionSound().play(MediaActionSound.STOP_VIDEO_RECORDING);
+ }
+
+ setRecordState(Record.READY_FOR_RECORD_STATE);
+ getCameraController().stopVideoRecord();
+
+ this.onStopVideoRecord();
+
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onRecordStateVideoReadyForRecord();
+ }
+ }
+
+ public CameraController getCameraController() {
+ return cameraController;
+ }
+
+ public void clearCameraPreview() {
+ if (previewContainer != null)
+ previewContainer.removeAllViews();
+ }
+
+ public void setCameraPreview(View preview, Size previewSize) {
+ //onCameraControllerReady()
+ videoQualities = cameraController.getVideoQualityOptions();
+ photoQualities = cameraController.getPhotoQualityOptions();
+
+ if (previewContainer == null || preview == null) return;
+ previewContainer.removeAllViews();
+ previewContainer.addView(preview);
+
+ previewContainer.setAspectRatio(previewSize.getHeight() / (double) previewSize.getWidth());
+ }
+
+ public void setMediaFilePath(final File mediaFile) {
+ this.mediaFilePath = mediaFile.toString();
+ }
+
+ public void onStartVideoRecord(final File mediaFile) {
+ setMediaFilePath(mediaFile);
+ if (maxVideoFileSize > 0) {
+
+ if (cameraFragmentVideoRecordTextListener != null) {
+ cameraFragmentVideoRecordTextListener.setRecordSizeText(maxVideoFileSize, "1Mb" + " / " + maxVideoFileSize / (1024 * 1024) + "Mb");
+ cameraFragmentVideoRecordTextListener.setRecordSizeTextVisible(true);
+ }
+ try {
+ fileObserver = new FileObserver(this.mediaFilePath) {
+ private long lastUpdateSize = 0;
+
+ @Override
+ public void onEvent(int event, String path) {
+ final long fileSize = mediaFile.length() / (1024 * 1024);
+ if ((fileSize - lastUpdateSize) >= 1) {
+ lastUpdateSize = fileSize;
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ if (cameraFragmentVideoRecordTextListener != null) {
+ cameraFragmentVideoRecordTextListener.setRecordSizeText(maxVideoFileSize, fileSize + "Mb" + " / " + maxVideoFileSize / (1024 * 1024) + "Mb");
+ }
+ }
+ });
+ }
+ }
+ };
+ fileObserver.startWatching();
+ } catch (Exception e) {
+ Log.e("FileObserver", "setMediaFilePath: ", e);
+ }
+ }
+ countDownTimer.start();
+
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onStartVideoRecord(mediaFile);
+ }
+ }
+
+ public void onStopVideoRecord() {
+ if (cameraFragmentControlsListener != null) {
+ cameraFragmentControlsListener.allowRecord(false);
+ }
+ if (cameraFragmentStateListener != null) {
+ cameraFragmentStateListener.onStopVideoRecord();
+ }
+ setRecordState(Record.READY_FOR_RECORD_STATE);
+
+ if (fileObserver != null)
+ fileObserver.stopWatching();
+ countDownTimer.stop();
+
+ final int mediaAction = configurationProvider.getMediaAction();
+ if (cameraFragmentControlsListener != null) {
+ if (mediaAction != Configuration.MEDIA_ACTION_UNSPECIFIED) {
+ cameraFragmentControlsListener.setMediaActionSwitchVisible(false);
+ } else {
+ cameraFragmentControlsListener.setMediaActionSwitchVisible(true);
+ }
+ }
+
+ if (cameraFragmentResultListener != null) {
+ final String filePath = getCameraController().getOutputFile().toString();
+ cameraFragmentResultListener.onVideoRecorded(filePath);
+ }
+ }
+
+ public void setStateListener(CameraFragmentStateListener cameraFragmentStateListener) {
+ this.cameraFragmentStateListener = cameraFragmentStateListener;
+ }
+
+ public void setTextListener(CameraFragmentVideoRecordTextListener cameraFragmentVideoRecordTextListener) {
+ this.cameraFragmentVideoRecordTextListener = cameraFragmentVideoRecordTextListener;
+ }
+
+ public void setControlsListener(CameraFragmentControlsListener cameraFragmentControlsListener) {
+ this.cameraFragmentControlsListener = cameraFragmentControlsListener;
+ }
+
+ public void setResultListener(CameraFragmentResultListener cameraFragmentResultListener) {
+ this.cameraFragmentResultListener = cameraFragmentResultListener;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/PhotoQualityOption.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/PhotoQualityOption.java
new file mode 100644
index 0000000..5814a73
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/PhotoQualityOption.java
@@ -0,0 +1,46 @@
+package com.github.florent37.camerafragment.internal.ui.model;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+import com.github.florent37.camerafragment.internal.utils.Size;
+
+/**
+ * Created by memfis on 12/1/16.
+ */
+
+public class PhotoQualityOption implements CharSequence {
+
+ @Configuration.MediaQuality
+ private int mediaQuality;
+ private String title;
+
+ public PhotoQualityOption(@Configuration.MediaQuality int mediaQuality, Size size) {
+ this.mediaQuality = mediaQuality;
+
+ title = String.valueOf(size.getWidth()) + " x " + String.valueOf(size.getHeight());
+ }
+
+ @Configuration.MediaQuality
+ public int getMediaQuality() {
+ return mediaQuality;
+ }
+
+ @Override
+ public int length() {
+ return title.length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ return title.charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return title.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/VideoQualityOption.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/VideoQualityOption.java
new file mode 100644
index 0000000..4ec27b5
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/model/VideoQualityOption.java
@@ -0,0 +1,59 @@
+package com.github.florent37.camerafragment.internal.ui.model;
+
+import android.media.CamcorderProfile;
+
+import java.util.concurrent.TimeUnit;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+
+/**
+ * Created by memfis on 12/1/16.
+ */
+
+public class VideoQualityOption implements CharSequence {
+
+ private String title;
+
+ @Configuration.MediaQuality
+ private int mediaQuality;
+
+ public VideoQualityOption(@Configuration.MediaQuality int mediaQuality, CamcorderProfile camcorderProfile, double videoDuration) {
+ this.mediaQuality = mediaQuality;
+
+ long minutes = TimeUnit.SECONDS.toMinutes((long) videoDuration);
+ long seconds = ((long) videoDuration) - minutes * 60;
+
+ if (mediaQuality == Configuration.MEDIA_QUALITY_AUTO) {
+ title = "Auto " + ", (" + (minutes > 10 ? minutes : ("0" + minutes)) + ":" + (seconds > 10 ? seconds : ("0" + seconds)) + " min)";
+ } else {
+ title = String.valueOf(camcorderProfile.videoFrameWidth)
+ + " x " + String.valueOf(camcorderProfile.videoFrameHeight)
+ + (videoDuration <= 0 ? "" : ", (" + (minutes > 10 ? minutes : ("0" + minutes)) + ":" + (seconds > 10 ? seconds : ("0" + seconds)) + " min)");
+ }
+ }
+
+ @Configuration.MediaQuality
+ public int getMediaQuality() {
+ return mediaQuality;
+ }
+
+ @Override
+ public int length() {
+ return title.length();
+ }
+
+ @Override
+ public char charAt(int index) {
+ return title.charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return title.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+}
\ No newline at end of file
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AspectFrameLayout.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AspectFrameLayout.java
new file mode 100644
index 0000000..8d169cc
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AspectFrameLayout.java
@@ -0,0 +1,67 @@
+package com.github.florent37.camerafragment.internal.ui.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+/**
+ * Layout that adjusts to maintain a specific aspect ratio.
+ */
+public class AspectFrameLayout extends FrameLayout {
+
+ private static final String TAG = "AspectFrameLayout";
+
+ private double targetAspectRatio = -1.0; // initially use default window size
+
+ public AspectFrameLayout(Context context) {
+ super(context);
+ }
+
+ public AspectFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setAspectRatio(double aspectRatio) {
+ if (aspectRatio < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ if (targetAspectRatio != aspectRatio) {
+ targetAspectRatio = aspectRatio;
+ requestLayout();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+
+ if (targetAspectRatio > 0) {
+ int initialWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int initialHeight = MeasureSpec.getSize(heightMeasureSpec);
+
+ // padding
+ int horizontalPadding = getPaddingLeft() + getPaddingRight();
+ int verticalPadding = getPaddingTop() + getPaddingBottom();
+ initialWidth -= horizontalPadding;
+ initialHeight -= verticalPadding;
+
+ double viewAspectRatio = (double) initialWidth / initialHeight;
+ double aspectDifference = targetAspectRatio / viewAspectRatio - 1;
+
+ if (Math.abs(aspectDifference) < 0.01) {
+ //no changes
+ } else {
+ if (aspectDifference > 0) {
+ initialHeight = (int) (initialWidth / targetAspectRatio);
+ } else {
+ initialWidth = (int) (initialHeight * targetAspectRatio);
+ }
+ initialWidth += horizontalPadding;
+ initialHeight += verticalPadding;
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(initialWidth, MeasureSpec.EXACTLY);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(initialHeight, MeasureSpec.EXACTLY);
+ }
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitSurfaceView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitSurfaceView.java
new file mode 100644
index 0000000..96de853
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitSurfaceView.java
@@ -0,0 +1,64 @@
+package com.github.florent37.camerafragment.internal.ui.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+@SuppressWarnings("deprecation")
+@SuppressLint("ViewConstructor")
+public class AutoFitSurfaceView extends SurfaceView {
+
+ private final static String TAG = "AutoFitSurfaceView";
+
+ private final SurfaceHolder surfaceHolder;
+
+ private int ratioWidth;
+ private int ratioHeight;
+
+ public AutoFitSurfaceView(@NonNull Context context, SurfaceHolder.Callback callback) {
+ super(context);
+
+ this.surfaceHolder = getHolder();
+
+ this.surfaceHolder.addCallback(callback);
+ this.surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+ }
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated fromList the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ public void setAspectRatio(int width, int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Size cannot be negative.");
+ }
+ ratioWidth = width;
+ ratioHeight = height;
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
+ final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
+
+ if (0 == ratioWidth || 0 == ratioHeight) {
+ setMeasuredDimension(width, height);
+ } else {
+ if (width < height * (ratioWidth / (float) ratioHeight)) {
+ setMeasuredDimension(width, (int) (width * (ratioWidth / (float) ratioHeight)));
+ } else {
+ setMeasuredDimension((int) (height * (ratioWidth / (float) ratioHeight)), height);
+ }
+ }
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitTextureView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitTextureView.java
new file mode 100644
index 0000000..0402b43
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/ui/view/AutoFitTextureView.java
@@ -0,0 +1,59 @@
+package com.github.florent37.camerafragment.internal.ui.view;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.view.TextureView;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+@SuppressLint("ViewConstructor")
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class AutoFitTextureView extends TextureView {
+
+ private final static String TAG = "AutoFitTextureView";
+
+ private int ratioWidth = 0;
+ private int ratioHeight = 0;
+
+ public AutoFitTextureView(Context context, TextureView.SurfaceTextureListener surfaceTextureListener) {
+ super(context, null);
+ setSurfaceTextureListener(surfaceTextureListener);
+ }
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated fromList the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ public void setAspectRatio(int width, int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Size cannot be negative.");
+ }
+ ratioWidth = width;
+ ratioHeight = height;
+
+ requestLayout();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
+ final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
+
+ if (0 == ratioWidth || 0 == ratioHeight) {
+ setMeasuredDimension(width, height);
+ } else {
+ if (width < height * (ratioWidth / (float) ratioHeight)) {
+ setMeasuredDimension(width, (int) (width * (ratioWidth / (float) ratioHeight)));
+ } else {
+ setMeasuredDimension((int) (height * (ratioWidth / (float) ratioHeight)), height);
+ }
+ }
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/CameraHelper.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/CameraHelper.java
new file mode 100644
index 0000000..1a25fef
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/CameraHelper.java
@@ -0,0 +1,465 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.media.CamcorderProfile;
+import android.os.Build;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import com.github.florent37.camerafragment.configuration.Configuration;
+
+/**
+ * Created by memfis on 7/6/16.
+ *
+ * Class with some common methods to work with camera.
+ */
+public final class CameraHelper {
+
+ public final static String TAG = "CameraHelper";
+
+
+ private CameraHelper() {
+
+ }
+
+ public static boolean hasCamera(Context context) {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static boolean hasCamera2(Context context) {
+ if (context == null) return false;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return false;
+ try {
+ CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ String[] idList = manager.getCameraIdList();
+ boolean notNull = true;
+ if (idList.length == 0) {
+ notNull = false;
+ } else {
+ for (final String str : idList) {
+ if (str == null || str.trim().isEmpty()) {
+ notNull = false;
+ break;
+ }
+ final CameraCharacteristics characteristics = manager.getCameraCharacteristics(str);
+
+ final int supportLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+ if (supportLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
+ notNull = false;
+ break;
+ }
+ }
+ }
+ return notNull;
+ } catch (Throwable ignore) {
+ return false;
+ }
+ }
+
+ public static File getOutputMediaFile(Context context, @Configuration.MediaAction int mediaAction) {
+ File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES), context.getPackageName());
+
+ if (!mediaStorageDir.exists()) {
+ if (!mediaStorageDir.mkdirs()) {
+ Log.d(TAG, "Failed to create directory.");
+ return null;
+ }
+ }
+
+ String timeStamp = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss").format(new Date());
+ File mediaFile;
+ if (mediaAction == Configuration.MEDIA_ACTION_PHOTO) {
+ mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+ "IMG_" + timeStamp + ".jpg");
+ } else if (mediaAction == Configuration.MEDIA_ACTION_VIDEO) {
+ mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+ "VID_" + timeStamp + ".mp4");
+ } else {
+ return null;
+ }
+
+ return mediaFile;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Size getPictureSize(List choices, @Configuration.MediaQuality int mediaQuality) {
+ if (choices == null || choices.isEmpty()) return null;
+ if (choices.size() == 1) return choices.get(0);
+
+ Size result = null;
+ Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2());
+ Size minPictureSize = Collections.min(choices, new CompareSizesByArea2());
+
+ Collections.sort(choices, new CompareSizesByArea2());
+
+ if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) {
+ result = maxPictureSize;
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) {
+ if (choices.size() == 2) result = minPictureSize;
+ else {
+ int half = choices.size() / 2;
+ int lowQualityIndex = (choices.size() - half) / 2;
+ result = choices.get(lowQualityIndex + 1);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) {
+ if (choices.size() == 2) result = maxPictureSize;
+ else {
+ int half = choices.size() / 2;
+ int highQualityIndex = (choices.size() - half) / 2;
+ result = choices.get(choices.size() - highQualityIndex - 1);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) {
+ if (choices.size() == 2) result = minPictureSize;
+ else {
+ int mediumQualityIndex = choices.size() / 2;
+ result = choices.get(mediumQualityIndex);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) {
+ result = minPictureSize;
+ }
+
+ return result;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Size getPictureSize(Size[] sizes, @Configuration.MediaQuality int mediaQuality) {
+ if (sizes == null || sizes.length == 0) return null;
+
+ List choices = Arrays.asList(sizes);
+
+ if (choices.size() == 1) return choices.get(0);
+
+ Size result = null;
+ Size maxPictureSize = Collections.max(choices, new CompareSizesByArea2());
+ Size minPictureSize = Collections.min(choices, new CompareSizesByArea2());
+
+ Collections.sort(choices, new CompareSizesByArea2());
+
+ if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) {
+ result = maxPictureSize;
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) {
+ if (choices.size() == 2) result = minPictureSize;
+ else {
+ int half = choices.size() / 2;
+ int lowQualityIndex = (choices.size() - half) / 2;
+ result = choices.get(lowQualityIndex + 1);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) {
+ if (choices.size() == 2) result = maxPictureSize;
+ else {
+ int half = choices.size() / 2;
+ int highQualityIndex = (choices.size() - half) / 2;
+ result = choices.get(choices.size() - highQualityIndex - 1);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) {
+ if (choices.size() == 2) result = minPictureSize;
+ else {
+ int mediumQualityIndex = choices.size() / 2;
+ result = choices.get(mediumQualityIndex);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) {
+ result = minPictureSize;
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Size getOptimalPreviewSize(List sizes, int width, int height) {
+ final double ASPECT_TOLERANCE = 0.1;
+ double targetRatio = (double) height / width;
+
+ if (sizes == null) return null;
+
+ Size optimalSize = null;
+ double minDiff = Double.MAX_VALUE;
+
+ int targetHeight = height;
+
+ for (Size size : sizes) {
+ double ratio = (double) size.getWidth() / size.getHeight();
+ if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+
+ if (optimalSize == null) {
+ minDiff = Double.MAX_VALUE;
+ for (Size size : sizes) {
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Size getSizeWithClosestRatio(List sizes, int width, int height) {
+
+ if (sizes == null) return null;
+
+ double MIN_TOLERANCE = 100;
+ double targetRatio = (double) height / width;
+ Size optimalSize = null;
+ double minDiff = Double.MAX_VALUE;
+
+ int targetHeight = height;
+
+ for (Size size : sizes) {
+ if (size.getWidth() == width && size.getHeight() == height)
+ return size;
+
+ double ratio = (double) size.getHeight() / size.getWidth();
+
+ if (Math.abs(ratio - targetRatio) < MIN_TOLERANCE) MIN_TOLERANCE = ratio;
+ else continue;
+
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+
+ if (optimalSize == null) {
+ minDiff = Double.MAX_VALUE;
+ for (Size size : sizes) {
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Size getOptimalPreviewSize(Size[] sizes, int width, int height) {
+
+ if (sizes == null) return null;
+
+ final double ASPECT_TOLERANCE = 0.1;
+ double targetRatio = (double) height / width;
+ Size optimalSize = null;
+ double minDiff = Double.MAX_VALUE;
+
+ int targetHeight = height;
+
+ for (Size size : sizes) {
+// if (size.getWidth() == width && size.getHeight() == height)
+// return size;
+ double ratio = (double) size.getWidth() / size.getHeight();
+ if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+
+ if (optimalSize == null) {
+ minDiff = Double.MAX_VALUE;
+ for (Size size : sizes) {
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Size getSizeWithClosestRatio(Size[] sizes, int width, int height) {
+
+ if (sizes == null) return null;
+
+ double MIN_TOLERANCE = 100;
+ double targetRatio = (double) height / width;
+ Size optimalSize = null;
+ double minDiff = Double.MAX_VALUE;
+
+ int targetHeight = height;
+
+ for (Size size : sizes) {
+// if (size.getWidth() == width && size.getHeight() == height)
+// return size;
+
+ double ratio = (double) size.getHeight() / size.getWidth();
+
+ if (Math.abs(ratio - targetRatio) < MIN_TOLERANCE) MIN_TOLERANCE = ratio;
+ else continue;
+
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+
+ if (optimalSize == null) {
+ minDiff = Double.MAX_VALUE;
+ for (Size size : sizes) {
+ if (Math.abs(size.getHeight() - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.getHeight() - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ List bigEnough = new ArrayList<>();
+ int w = aspectRatio.getWidth();
+ int h = aspectRatio.getHeight();
+ for (Size option : choices) {
+ if (option.getHeight() == option.getWidth() * h / w &&
+ option.getWidth() >= width && option.getHeight() >= height) {
+ bigEnough.add(option);
+ }
+ }
+
+ // Pick the smallest of those, assuming we found any
+ if (bigEnough.size() > 0) {
+ return Collections.min(bigEnough, new CompareSizesByArea2());
+ } else {
+ Log.e(TAG, "Couldn't find any suitable preview size");
+ return null;
+ }
+ }
+
+ private static double calculateApproximateVideoSize(CamcorderProfile camcorderProfile, int seconds) {
+ return ((camcorderProfile.videoBitRate / (float) 1 + camcorderProfile.audioBitRate / (float) 1) * seconds) / (float) 8;
+ }
+
+ public static double calculateApproximateVideoDuration(CamcorderProfile camcorderProfile, long maxFileSize) {
+ return 8 * maxFileSize / (camcorderProfile.videoBitRate + camcorderProfile.audioBitRate);
+ }
+
+ private static long calculateMinimumRequiredBitRate(CamcorderProfile camcorderProfile, long maxFileSize, int seconds) {
+ return 8 * maxFileSize / seconds - camcorderProfile.audioBitRate;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static CamcorderProfile getCamcorderProfile(String cameraId, long maximumFileSize, int minimumDurationInSeconds) {
+ if (TextUtils.isEmpty(cameraId)) {
+ return null;
+ }
+ int cameraIdInt = Integer.parseInt(cameraId);
+ return getCamcorderProfile(cameraIdInt, maximumFileSize, minimumDurationInSeconds);
+ }
+
+ public static CamcorderProfile getCamcorderProfile(int currentCameraId, long maximumFileSize, int minimumDurationInSeconds) {
+ if (maximumFileSize <= 0)
+ return CamcorderProfile.get(currentCameraId, Configuration.MEDIA_QUALITY_HIGHEST);
+
+ int[] qualities = new int[]{Configuration.MEDIA_QUALITY_HIGHEST,
+ Configuration.MEDIA_QUALITY_HIGH, Configuration.MEDIA_QUALITY_MEDIUM,
+ Configuration.MEDIA_QUALITY_LOW, Configuration.MEDIA_QUALITY_LOWEST};
+
+ CamcorderProfile camcorderProfile;
+ for (int i = 0; i < qualities.length; ++i) {
+ camcorderProfile = CameraHelper.getCamcorderProfile(qualities[i], currentCameraId);
+ double fileSize = CameraHelper.calculateApproximateVideoSize(camcorderProfile, minimumDurationInSeconds);
+
+ if (fileSize > maximumFileSize) {
+ long minimumRequiredBitRate = calculateMinimumRequiredBitRate(camcorderProfile, maximumFileSize, minimumDurationInSeconds);
+
+ if (minimumRequiredBitRate >= camcorderProfile.videoBitRate / 4 && minimumRequiredBitRate <= camcorderProfile.videoBitRate) {
+ camcorderProfile.videoBitRate = (int) minimumRequiredBitRate;
+ return camcorderProfile;
+ }
+ } else return camcorderProfile;
+ }
+ return CameraHelper.getCamcorderProfile(Configuration.MEDIA_QUALITY_LOWEST, currentCameraId);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static CamcorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, String cameraId) {
+ if (TextUtils.isEmpty(cameraId)) {
+ return null;
+ }
+ int cameraIdInt = Integer.parseInt(cameraId);
+ return getCamcorderProfile(mediaQuality, cameraIdInt);
+ }
+
+ public static CamcorderProfile getCamcorderProfile(@Configuration.MediaQuality int mediaQuality, int cameraId) {
+ if (Build.VERSION.SDK_INT > 10) {
+ if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) {
+ if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P);
+ } else if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P);
+ } else {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) {
+ if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P);
+ } else if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P);
+ } else {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) {
+ if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P);
+ } else {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ }
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ } else {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ }
+ } else {
+ if (mediaQuality == Configuration.MEDIA_QUALITY_HIGHEST) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_HIGH) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_MEDIUM) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOW) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ } else if (mediaQuality == Configuration.MEDIA_QUALITY_LOWEST) {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
+ } else {
+ return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static class CompareSizesByArea2 implements Comparator {
+ @Override
+ public int compare(Size lhs, Size rhs) {
+ // We cast here to ensure the multiplications won't overflow
+ return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+ (long) rhs.getWidth() * rhs.getHeight());
+ }
+
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/DateTimeUtils.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/DateTimeUtils.java
new file mode 100644
index 0000000..d9e32b3
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/DateTimeUtils.java
@@ -0,0 +1,12 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+/**
+ * Created by memfis on 6/27/16.
+ */
+public final class DateTimeUtils {
+
+ public static final long SECOND = 1000;
+ public static final long MINUTE = 60 * SECOND;
+ public static final long HOUR = 60 * MINUTE;
+ public static final long DAY = 24 * HOUR;
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageLoader.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageLoader.java
new file mode 100644
index 0000000..8890c05
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageLoader.java
@@ -0,0 +1,196 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.media.ExifInterface;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.support.annotation.NonNull;
+import android.view.Display;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import java.io.IOException;
+
+/**
+ * Created by memfis on 7/18/16.
+ */
+public final class ImageLoader {
+
+ private Context context;
+ private String url;
+
+ private ImageLoader(Context context) {
+ this.context = context;
+ }
+
+ public static class Builder {
+
+ private ImageLoader imageLoader;
+
+ public Builder(@NonNull Context context) {
+ imageLoader = new ImageLoader(context);
+ }
+
+ public Builder load(String url) {
+ imageLoader.url = url;
+ return this;
+ }
+
+ public ImageLoader build() {
+ return imageLoader;
+ }
+ }
+
+ public void into(final ImageView target) {
+ ViewTreeObserver viewTreeObserver = target.getViewTreeObserver();
+ viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ target.getViewTreeObserver().removeOnPreDrawListener(this);
+
+ new ImageLoaderThread(target, url).start();
+
+ return true;
+ }
+ });
+ }
+
+ private class ImageLoaderThread extends Thread {
+
+ private ImageView target;
+ private String url;
+ private Handler mainHandler = new Handler(Looper.getMainLooper());
+
+ private ImageLoaderThread(ImageView target, String url) {
+ this.target = target;
+ this.url = url;
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+
+ int imageViewHeight;
+ int imageViewWidth;
+
+ if (Build.VERSION.SDK_INT < 13) {
+ imageViewHeight = display.getHeight();
+ imageViewWidth = display.getWidth();
+ } else {
+ Point size = new Point();
+ display.getSize(size);
+ imageViewHeight = size.y;
+ imageViewWidth = size.x;
+ }
+
+// int imageViewHeight = target.getMeasuredHeight();
+// int imageViewWidth = target.getMeasuredWidth();
+
+ Bitmap decodedBitmap = decodeSampledBitmapFromResource(url, imageViewWidth, imageViewHeight);
+ final Bitmap resultBitmap = rotateBitmap(decodedBitmap, getExifOrientation());
+
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ target.setImageBitmap(resultBitmap);
+ }
+ });
+ }
+
+ private Bitmap rotateBitmap(Bitmap bitmap, int orientation) {
+ Matrix matrix = new Matrix();
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_NORMAL:
+ return bitmap;
+ case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
+ matrix.setScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ matrix.setRotate(180);
+ break;
+ case ExifInterface.ORIENTATION_FLIP_VERTICAL:
+ matrix.setRotate(180);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_TRANSPOSE:
+ matrix.setRotate(90);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ matrix.setRotate(90);
+ break;
+ case ExifInterface.ORIENTATION_TRANSVERSE:
+ matrix.setRotate(-90);
+ matrix.postScale(-1, 1);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ matrix.setRotate(-90);
+ break;
+ default:
+ return bitmap;
+ }
+
+ try {
+ Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+ bitmap.recycle();
+ return bmRotated;
+ } catch (OutOfMemoryError ignore) {
+ return null;
+ }
+ }
+
+ private int getExifOrientation() {
+ ExifInterface exif = null;
+ try {
+ exif = new ExifInterface(url);
+ } catch (IOException ignore) {
+ }
+ return exif == null ? ExifInterface.ORIENTATION_UNDEFINED :
+ exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+ }
+
+ private Bitmap decodeSampledBitmapFromResource(String url,
+ int requestedWidth, int requestedHeight) {
+
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(url, options);
+
+ options.inSampleSize = calculateInSampleSize(options, requestedWidth, requestedHeight);
+ options.inJustDecodeBounds = false;
+ options.inPreferredConfig = Bitmap.Config.RGB_565;
+ return BitmapFactory.decodeFile(url, options);
+ }
+
+ private int calculateInSampleSize(BitmapFactory.Options options,
+ int requestedWidth, int requestedHeight) {
+
+ final int height = options.outHeight;
+ final int width = options.outWidth;
+ int inSampleSize = 1;
+
+ if (height > requestedHeight || width > requestedWidth) {
+
+ final int halfHeight = height / inSampleSize;
+ final int halfWidth = width / inSampleSize;
+
+ while ((halfHeight / inSampleSize) > requestedHeight
+ && (halfWidth / inSampleSize) > requestedWidth) {
+ inSampleSize *= 2;
+ }
+ }
+ return inSampleSize;
+ }
+ }
+
+}
+
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageSaver.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageSaver.java
new file mode 100644
index 0000000..7dbb7da
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/ImageSaver.java
@@ -0,0 +1,62 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+import android.annotation.TargetApi;
+import android.media.Image;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public class ImageSaver implements Runnable {
+
+ private final static String TAG = "ImageSaver";
+
+ private final Image image;
+ private final File file;
+ private ImageSaverCallback imageSaverCallback;
+
+ public interface ImageSaverCallback {
+ void onSuccessFinish(byte[] bytes);
+
+ void onError();
+ }
+
+ public ImageSaver(Image image, File file, ImageSaverCallback imageSaverCallback) {
+ this.image = image;
+ this.file = file;
+ this.imageSaverCallback = imageSaverCallback;
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ @Override
+ public void run() {
+ ByteBuffer buffer = image.getPlanes()[0].getBuffer();
+ byte[] bytes = new byte[buffer.remaining()];
+ buffer.get(bytes);
+ FileOutputStream output = null;
+ try {
+ output = new FileOutputStream(file);
+ output.write(bytes);
+ imageSaverCallback.onSuccessFinish(bytes);
+ } catch (IOException ignore) {
+ Log.e(TAG, "Can't save the image file.");
+ imageSaverCallback.onError();
+ } finally {
+ image.close();
+ if (null != output) {
+ try {
+ output.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Can't release image or close the output stream.");
+ }
+ }
+ }
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Size.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Size.java
new file mode 100644
index 0000000..2623974
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Size.java
@@ -0,0 +1,104 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+import android.annotation.TargetApi;
+import android.hardware.Camera;
+import android.os.Build;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by memfis on 12/1/16.
+ */
+
+public class Size {
+
+ private int width;
+ private int height;
+
+ public Size() {
+ width = 0;
+ height = 0;
+ }
+
+ public Size(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public Size(android.util.Size size) {
+ this.width = size.getWidth();
+ this.height = size.getHeight();
+ }
+
+ @SuppressWarnings("deprecation")
+ public Size(Camera.Size size) {
+ this.width = size.width;
+ this.height = size.height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static List fromList2(List sizes) {
+ if (sizes == null) return null;
+ List result = new ArrayList<>(sizes.size());
+
+ for (android.util.Size size : sizes) {
+ result.add(new Size(size));
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static List fromList(List sizes) {
+ if (sizes == null) return null;
+ List result = new ArrayList<>(sizes.size());
+
+ for (Camera.Size size : sizes) {
+ result.add(new Size(size));
+ }
+
+ return result;
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static Size[] fromArray2(android.util.Size[] sizes) {
+ if (sizes == null) return null;
+ Size[] result = new Size[sizes.length];
+
+ for (int i = 0; i < sizes.length; ++i) {
+ result[i] = new Size(sizes[i]);
+ }
+
+ return result;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Size[] fromArray(Camera.Size[] sizes) {
+ if (sizes == null) return null;
+ Size[] result = new Size[sizes.length];
+
+ for (int i = 0; i < sizes.length; ++i) {
+ result[i] = new Size(sizes[i]);
+ }
+
+ return result;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Utils.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Utils.java
new file mode 100644
index 0000000..bc774c8
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/internal/utils/Utils.java
@@ -0,0 +1,53 @@
+package com.github.florent37.camerafragment.internal.utils;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.webkit.MimeTypeMap;
+
+/**
+ * Created by memfis on 7/18/16.
+ */
+public class Utils {
+
+ public static int getDeviceDefaultOrientation(Context context) {
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Configuration config = context.getResources().getConfiguration();
+
+ int rotation = windowManager.getDefaultDisplay().getRotation();
+
+ if (((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) &&
+ config.orientation == Configuration.ORIENTATION_LANDSCAPE)
+ || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) &&
+ config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
+ return Configuration.ORIENTATION_LANDSCAPE;
+ } else {
+ return Configuration.ORIENTATION_PORTRAIT;
+ }
+ }
+
+ public static String getMimeType(String url) {
+ String type = "";
+ String extension = MimeTypeMap.getFileExtensionFromUrl(url);
+ if (!TextUtils.isEmpty(extension)) {
+ type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ } else {
+ String reCheckExtension = MimeTypeMap.getFileExtensionFromUrl(url.replaceAll("\\s+", ""));
+ if (!TextUtils.isEmpty(reCheckExtension)) {
+ type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(reCheckExtension);
+ }
+ }
+ return type;
+ }
+
+ public static int convertDipToPixels(Context context, int dip) {
+ Resources resources = context.getResources();
+ float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, resources.getDisplayMetrics());
+ return (int) px;
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentControlsListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentControlsListener.java
new file mode 100644
index 0000000..f8065ef
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentControlsListener.java
@@ -0,0 +1,13 @@
+package com.github.florent37.camerafragment.listeners;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public interface CameraFragmentControlsListener {
+ void lockControls();
+ void unLockControls();
+ void allowCameraSwitching(boolean allow);
+ void allowRecord(boolean allow);
+ void setMediaActionSwitchVisible(boolean visible);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentResultListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentResultListener.java
new file mode 100644
index 0000000..c57410c
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentResultListener.java
@@ -0,0 +1,14 @@
+package com.github.florent37.camerafragment.listeners;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public interface CameraFragmentResultListener {
+
+ //Called when the video record is finished and saved
+ void onVideoRecorded(String filePath);
+
+ //called when the photo is taken and saved
+ void onPhotoTaken(byte[] bytes, String filePath);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentStateListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentStateListener.java
new file mode 100644
index 0000000..c216210
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentStateListener.java
@@ -0,0 +1,42 @@
+package com.github.florent37.camerafragment.listeners;
+
+import java.io.File;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public interface CameraFragmentStateListener {
+
+ //when the current displayed camera is the back
+ void onCurrentCameraBack();
+
+ //when the current displayed camera is the front
+ void onCurrentCameraFront();
+
+ //when the flash is at mode auto
+ void onFlashAuto();
+ //when the flash is at on
+ void onFlashOn();
+ //when the flash is off
+ void onFlashOff();
+
+ //if the camera is ready to take a photo
+ void onCameraSetupForPhoto();
+
+ //if the camera is ready to take a video
+ void onCameraSetupForVideo();
+
+ //when the camera state is "ready to record a video"
+ void onRecordStateVideoReadyForRecord();
+ //when the camera state is "recording a video"
+ void onRecordStateVideoInProgress();
+ //when the camera state is "ready to take a photo"
+ void onRecordStatePhoto();
+
+ //after the rotation of the screen / camera
+ void shouldRotateControls(int degrees);
+
+ void onStartVideoRecord(File outputFile);
+ void onStopVideoRecord();
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentVideoRecordTextListener.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentVideoRecordTextListener.java
new file mode 100644
index 0000000..95a34cc
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/listeners/CameraFragmentVideoRecordTextListener.java
@@ -0,0 +1,13 @@
+package com.github.florent37.camerafragment.listeners;
+
+/**
+ * Created by florentchampigny on 13/01/2017.
+ */
+
+public interface CameraFragmentVideoRecordTextListener {
+ void setRecordSizeText(long size, String text);
+ void setRecordSizeTextVisible(boolean visible);
+
+ void setRecordDurationText(String text);
+ void setRecordDurationTextVisible(boolean visible);
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSettingsView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSettingsView.java
new file mode 100644
index 0000000..aa4e9ed
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSettingsView.java
@@ -0,0 +1,42 @@
+package com.github.florent37.camerafragment.widgets;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+
+/**
+ * Created by memfis on 8/23/16.
+ */
+public class CameraSettingsView extends ImageButton {
+ public CameraSettingsView(Context context) {
+ super(context);
+ }
+
+ public CameraSettingsView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CameraSettingsView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public CameraSettingsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ if (Build.VERSION.SDK_INT > 10) {
+ if (enabled) {
+ setAlpha(1f);
+ } else {
+ setAlpha(0.5f);
+ }
+ }
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSwitchView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSwitchView.java
new file mode 100644
index 0000000..14d1064
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/CameraSwitchView.java
@@ -0,0 +1,93 @@
+package com.github.florent37.camerafragment.widgets;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v7.widget.AppCompatImageButton;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.github.florent37.camerafragment.R;
+import com.github.florent37.camerafragment.internal.utils.Utils;
+
+/**
+ * Created by memfis on 6/24/16.
+ */
+public class CameraSwitchView extends AppCompatImageButton {
+
+ @Nullable
+ private OnCameraTypeChangeListener onCameraTypeChangeListener;
+
+ public interface OnCameraTypeChangeListener {
+ void switchCameraType();
+ }
+
+ private Drawable frontCameraDrawable;
+ private Drawable rearCameraDrawable;
+ private int padding = 5;
+
+ public CameraSwitchView(Context context) {
+ this(context, null);
+ }
+
+ public CameraSwitchView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initializeView();
+ }
+
+ public CameraSwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs);
+ }
+
+ private void initializeView() {
+ Context context = getContext();
+ frontCameraDrawable = ContextCompat.getDrawable(context, R.drawable.ic_camera_front_white_24dp);
+ frontCameraDrawable = DrawableCompat.wrap(frontCameraDrawable);
+ DrawableCompat.setTintList(frontCameraDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector));
+
+ rearCameraDrawable = ContextCompat.getDrawable(context, R.drawable.ic_camera_rear_white_24dp);
+ rearCameraDrawable = DrawableCompat.wrap(rearCameraDrawable);
+ DrawableCompat.setTintList(rearCameraDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector));
+
+ setBackgroundResource(R.drawable.circle_frame_background_dark);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (onCameraTypeChangeListener != null) {
+ onCameraTypeChangeListener.switchCameraType();
+ }
+ }
+ });
+ displayBackCamera();
+
+ padding = Utils.convertDipToPixels(context, padding);
+ setPadding(padding, padding, padding, padding);
+ }
+
+ public void displayFrontCamera() {
+ setImageDrawable(frontCameraDrawable);
+ }
+
+ public void displayBackCamera() {
+ setImageDrawable(rearCameraDrawable);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (Build.VERSION.SDK_INT > 10) {
+ if (enabled) {
+ setAlpha(1f);
+ } else {
+ setAlpha(0.5f);
+ }
+ }
+ }
+
+ public void setOnCameraTypeChangeListener(OnCameraTypeChangeListener listener) {
+ this.onCameraTypeChangeListener = listener;
+ }
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/FlashSwitchView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/FlashSwitchView.java
new file mode 100644
index 0000000..029fc38
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/FlashSwitchView.java
@@ -0,0 +1,85 @@
+package com.github.florent37.camerafragment.widgets;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.github.florent37.camerafragment.R;
+
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public class FlashSwitchView extends ImageButton {
+
+ @Nullable
+ private FlashModeSwitchListener switchListener;
+
+ private Drawable flashOnDrawable;
+ private Drawable flashOffDrawable;
+ private Drawable flashAutoDrawable;
+
+ public interface FlashModeSwitchListener {
+ void toggleFlashMode();
+ }
+
+ public FlashSwitchView(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public FlashSwitchView(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ flashOnDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_on_white_24dp);
+ flashOffDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_off_white_24dp);
+ flashAutoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_flash_auto_white_24dp);
+ init();
+ }
+
+ private void init() {
+ setBackgroundColor(Color.TRANSPARENT);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (switchListener != null) {
+ switchListener.toggleFlashMode();
+ }
+ }
+ });
+ }
+
+ public void displayFlashOff() {
+ setImageDrawable(flashOffDrawable);
+ }
+
+ public void displayFlashOn() {
+ setImageDrawable(flashOnDrawable);
+ }
+
+ public void displayFlashAuto() {
+ setImageDrawable(flashAutoDrawable);
+ }
+
+ public void setFlashSwitchListener(@NonNull FlashModeSwitchListener switchListener) {
+ this.switchListener = switchListener;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (Build.VERSION.SDK_INT > 10) {
+ if (enabled) {
+ setAlpha(1f);
+ } else {
+ setAlpha(0.5f);
+ }
+ }
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/MediaActionSwitchView.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/MediaActionSwitchView.java
new file mode 100644
index 0000000..3a92f96
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/MediaActionSwitchView.java
@@ -0,0 +1,96 @@
+package com.github.florent37.camerafragment.widgets;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.github.florent37.camerafragment.R;
+import com.github.florent37.camerafragment.internal.utils.Utils;
+
+/**
+ * Created by memfis on 6/24/16.
+ */
+public class MediaActionSwitchView extends ImageButton {
+
+ @Nullable
+ private OnMediaActionStateChangeListener onMediaActionStateChangeListener;
+
+ public interface OnMediaActionStateChangeListener {
+ void switchAction();
+ }
+
+ private Drawable photoDrawable;
+ private Drawable videoDrawable;
+ private int padding = 5;
+
+ public MediaActionSwitchView(Context context) {
+ this(context, null);
+ }
+
+ public MediaActionSwitchView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initializeView();
+ }
+
+ public MediaActionSwitchView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs);
+ }
+
+ private void initializeView() {
+ Context context = getContext();
+
+ photoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_photo_camera_white_24dp);
+ photoDrawable = DrawableCompat.wrap(photoDrawable);
+ DrawableCompat.setTintList(photoDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector));
+
+ videoDrawable = ContextCompat.getDrawable(context, R.drawable.ic_videocam_white_24dp);
+ videoDrawable = DrawableCompat.wrap(videoDrawable);
+ DrawableCompat.setTintList(videoDrawable.mutate(), ContextCompat.getColorStateList(context, R.drawable.switch_camera_mode_selector));
+
+ setBackgroundResource(R.drawable.circle_frame_background_dark);
+// setBackgroundResource(R.drawable.circle_frame_background);
+
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(onMediaActionStateChangeListener != null) {
+ onMediaActionStateChangeListener.switchAction();
+ }
+ }
+ });
+
+ padding = Utils.convertDipToPixels(context, padding);
+ setPadding(padding, padding, padding, padding);
+ }
+
+ public void displayActionWillSwitchPhoto(){
+ setImageDrawable(photoDrawable);
+ }
+
+ public void displayActionWillSwitchVideo(){
+ setImageDrawable(videoDrawable);
+ }
+
+ public void setOnMediaActionStateChangeListener(OnMediaActionStateChangeListener onMediaActionStateChangeListener) {
+ this.onMediaActionStateChangeListener = onMediaActionStateChangeListener;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (Build.VERSION.SDK_INT > 10) {
+ if (enabled) {
+ setAlpha(1f);
+ } else {
+ setAlpha(0.5f);
+ }
+ }
+ }
+
+}
diff --git a/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/RecordButton.java b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/RecordButton.java
new file mode 100644
index 0000000..8630bc3
--- /dev/null
+++ b/camerafragment/src/main/java/com/github/florent37/camerafragment/widgets/RecordButton.java
@@ -0,0 +1,112 @@
+package com.github.florent37.camerafragment.widgets;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+
+import com.github.florent37.camerafragment.R;
+import com.github.florent37.camerafragment.internal.utils.Utils;
+
+/**
+ * Created by memfis on 7/6/16.
+ */
+public class RecordButton extends ImageButton {
+
+ public interface RecordButtonListener {
+ void onRecordButtonClicked();
+ }
+
+ private Drawable takePhotoDrawable;
+ private Drawable startRecordDrawable;
+ private Drawable stopRecordDrawable;
+ private int iconPadding = 8;
+ private int iconPaddingStop = 18;
+
+ @Nullable
+ private RecordButtonListener listener;
+
+ public RecordButton(@NonNull Context context) {
+ this(context, null, 0);
+ }
+
+ public RecordButton(@NonNull Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RecordButton(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ takePhotoDrawable = ContextCompat.getDrawable(context, R.drawable.take_photo_button);
+ startRecordDrawable = ContextCompat.getDrawable(context, R.drawable.start_video_record_button);
+ stopRecordDrawable = ContextCompat.getDrawable(context, R.drawable.stop_button_background);
+
+ if (Build.VERSION.SDK_INT > 15)
+ setBackground(ContextCompat.getDrawable(context, R.drawable.circle_frame_background));
+ else
+ setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.circle_frame_background));
+
+ setOnClickListener(new OnClickListener() {
+ private final static int CLICK_DELAY = 1000;
+
+ private long lastClickTime = 0;
+
+ @Override
+ public void onClick(View view) {
+ if (System.currentTimeMillis() - lastClickTime < CLICK_DELAY) {
+ return;
+ } else lastClickTime = System.currentTimeMillis();
+
+ if(listener != null) {
+ listener.onRecordButtonClicked();
+ }
+ }
+ });
+ setSoundEffectsEnabled(false);
+ setIconPadding(iconPadding);
+ }
+
+ //public void setup(@Configuration.MediaAction int mediaAction) {
+ // setMediaAction(mediaAction);
+ //}
+
+ private void setIconPadding(int paddingDP) {
+ int padding = Utils.convertDipToPixels(getContext(), paddingDP);
+ setPadding(padding, padding, padding, padding);
+ }
+
+ //public void setMediaAction(@Configuration.MediaAction int mediaAction) {
+ // this.mediaAction = mediaAction;
+ // if (listener != null) {
+ // if (Configuration.MEDIA_ACTION_PHOTO == mediaAction) {
+ // listener.setRecordState(Record.TAKE_PHOTO_STATE);
+ // } else {
+ // listener.setRecordState(Record.READY_FOR_RECORD_STATE);
+ // }
+ // }
+ //}
+
+ public void setRecordButtonListener(@NonNull RecordButtonListener listener) {
+ this.listener = listener;
+ }
+
+ public void displayVideoRecordStateReady(){
+ setImageDrawable(startRecordDrawable);
+ setIconPadding(iconPadding);
+ }
+
+ public void displayVideoRecordStateInProgress(){
+ setImageDrawable(stopRecordDrawable);
+ setIconPadding(iconPaddingStop);
+ }
+
+ public void displayPhotoState(){
+ setImageDrawable(takePhotoDrawable);
+ setIconPadding(iconPadding);
+ }
+
+}
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..f36dcd4
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_camera_front_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..4a5494f
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_camera_rear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_clear_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_clear_white_24dp.png
new file mode 100644
index 0000000..ceb1a1e
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_clear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_content_cut_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_content_cut_white_24dp.png
new file mode 100644
index 0000000..c32c747
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_content_cut_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_crop_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_crop_white_24dp.png
new file mode 100644
index 0000000..9bfca35
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_crop_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_done_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_done_white_24dp.png
new file mode 100644
index 0000000..c278b6c
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_done_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_flash_auto_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_flash_auto_white_24dp.png
new file mode 100644
index 0000000..3eb32b2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_flash_auto_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_flash_off_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_flash_off_white_24dp.png
new file mode 100644
index 0000000..948446b
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_flash_off_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_flash_on_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_flash_on_white_24dp.png
new file mode 100644
index 0000000..4747a11
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_flash_on_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_image_aspect_ratio_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_image_aspect_ratio_white_24dp.png
new file mode 100644
index 0000000..35a566c
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_image_aspect_ratio_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_photo_camera_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_photo_camera_white_24dp.png
new file mode 100644
index 0000000..497c88c
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_photo_camera_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_replay_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_replay_white_24dp.png
new file mode 100644
index 0000000..5ef425a
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_replay_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_settings_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_settings_white_24dp.png
new file mode 100644
index 0000000..97ded33
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_settings_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png b/camerafragment/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png
new file mode 100644
index 0000000..d83e0d5
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-hdpi/tile.9.png b/camerafragment/src/main/res/drawable-hdpi/tile.9.png
new file mode 100755
index 0000000..1358628
Binary files /dev/null and b/camerafragment/src/main/res/drawable-hdpi/tile.9.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..0392d64
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_camera_front_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..e1f0f1f
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_camera_rear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_clear_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_clear_white_24dp.png
new file mode 100644
index 0000000..af7f828
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_clear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_content_cut_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_content_cut_white_24dp.png
new file mode 100644
index 0000000..7eab26f
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_content_cut_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_crop_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_crop_white_24dp.png
new file mode 100644
index 0000000..8e78d8e
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_crop_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_done_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_done_white_24dp.png
new file mode 100644
index 0000000..6d84e14
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_done_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_flash_auto_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_flash_auto_white_24dp.png
new file mode 100644
index 0000000..2f7bcc3
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_flash_auto_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_flash_off_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_flash_off_white_24dp.png
new file mode 100644
index 0000000..156df5e
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_flash_off_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_flash_on_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_flash_on_white_24dp.png
new file mode 100644
index 0000000..9e57cde
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_flash_on_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_image_aspect_ratio_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_image_aspect_ratio_white_24dp.png
new file mode 100644
index 0000000..13241cd
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_image_aspect_ratio_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_photo_camera_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_photo_camera_white_24dp.png
new file mode 100644
index 0000000..e830522
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_photo_camera_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_replay_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_replay_white_24dp.png
new file mode 100644
index 0000000..5a79970
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_replay_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_settings_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_settings_white_24dp.png
new file mode 100644
index 0000000..8909c35
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_settings_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png b/camerafragment/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png
new file mode 100644
index 0000000..d146209
Binary files /dev/null and b/camerafragment/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..a80ccde
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_camera_front_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..b0768e3
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_camera_rear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png
new file mode 100644
index 0000000..b7c7ffd
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_content_cut_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_content_cut_white_24dp.png
new file mode 100644
index 0000000..6ddf0b6
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_content_cut_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png
new file mode 100644
index 0000000..859cdf7
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_crop_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_done_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_done_white_24dp.png
new file mode 100644
index 0000000..3b2b65d
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_done_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_flash_auto_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_auto_white_24dp.png
new file mode 100644
index 0000000..185fa40
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_auto_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_flash_off_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_off_white_24dp.png
new file mode 100644
index 0000000..23c854e
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_off_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_flash_on_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_on_white_24dp.png
new file mode 100644
index 0000000..bedc4eb
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_flash_on_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_image_aspect_ratio_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_image_aspect_ratio_white_24dp.png
new file mode 100644
index 0000000..e22ef2a
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_image_aspect_ratio_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_photo_camera_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_photo_camera_white_24dp.png
new file mode 100644
index 0000000..be9fb22
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_photo_camera_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_replay_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_replay_white_24dp.png
new file mode 100644
index 0000000..3b41913
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_replay_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png
new file mode 100644
index 0000000..5caedc8
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png b/camerafragment/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png
new file mode 100644
index 0000000..1b2583d
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..3eb24d1
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_front_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..8392b2a
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_camera_rear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png
new file mode 100644
index 0000000..6b717e0
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_content_cut_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_content_cut_white_24dp.png
new file mode 100644
index 0000000..006a5e2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_content_cut_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png
new file mode 100644
index 0000000..c0246cf
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_crop_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png
new file mode 100644
index 0000000..0ebb555
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_done_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_auto_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_auto_white_24dp.png
new file mode 100644
index 0000000..087aa59
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_auto_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_off_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_off_white_24dp.png
new file mode 100644
index 0000000..3cf30f3
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_off_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_on_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_on_white_24dp.png
new file mode 100644
index 0000000..4e116af
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_flash_on_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_image_aspect_ratio_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_image_aspect_ratio_white_24dp.png
new file mode 100644
index 0000000..b136fb2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_image_aspect_ratio_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_photo_camera_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_photo_camera_white_24dp.png
new file mode 100644
index 0000000..c8e69dc
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_photo_camera_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_replay_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_replay_white_24dp.png
new file mode 100644
index 0000000..fcddcf0
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_replay_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png
new file mode 100644
index 0000000..eabb0a2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png b/camerafragment/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png
new file mode 100644
index 0000000..44c28e2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png
new file mode 100644
index 0000000..951bc12
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_front_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png
new file mode 100644
index 0000000..8b2415b
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_camera_rear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png
new file mode 100644
index 0000000..3964192
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_content_cut_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_content_cut_white_24dp.png
new file mode 100644
index 0000000..06df922
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_content_cut_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png
new file mode 100644
index 0000000..3c027ef
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_crop_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png
new file mode 100644
index 0000000..d670618
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_done_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_auto_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_auto_white_24dp.png
new file mode 100644
index 0000000..899276f
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_auto_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_off_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_off_white_24dp.png
new file mode 100644
index 0000000..1d774f2
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_off_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_on_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_on_white_24dp.png
new file mode 100644
index 0000000..cb60611
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_flash_on_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_image_aspect_ratio_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_image_aspect_ratio_white_24dp.png
new file mode 100644
index 0000000..bd0863c
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_image_aspect_ratio_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png
new file mode 100644
index 0000000..777658e
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_photo_camera_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_replay_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_replay_white_24dp.png
new file mode 100644
index 0000000..1573fb1
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_replay_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png
new file mode 100644
index 0000000..507c5ed
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png b/camerafragment/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png
new file mode 100644
index 0000000..ed20c07
Binary files /dev/null and b/camerafragment/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png differ
diff --git a/camerafragment/src/main/res/drawable/circle_frame_background.xml b/camerafragment/src/main/res/drawable/circle_frame_background.xml
new file mode 100644
index 0000000..f1eaa1d
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/circle_frame_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/drawable/circle_frame_background_dark.xml b/camerafragment/src/main/res/drawable/circle_frame_background_dark.xml
new file mode 100644
index 0000000..1a2a2f1
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/circle_frame_background_dark.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/drawable/start_video_record_button.xml b/camerafragment/src/main/res/drawable/start_video_record_button.xml
new file mode 100644
index 0000000..470f51f
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/start_video_record_button.xml
@@ -0,0 +1,18 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/drawable/stop_button_background.xml b/camerafragment/src/main/res/drawable/stop_button_background.xml
new file mode 100644
index 0000000..7ee9a6f
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/stop_button_background.xml
@@ -0,0 +1,21 @@
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/drawable/switch_camera_mode_selector.xml b/camerafragment/src/main/res/drawable/switch_camera_mode_selector.xml
new file mode 100644
index 0000000..2399709
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/switch_camera_mode_selector.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/drawable/take_photo_button.xml b/camerafragment/src/main/res/drawable/take_photo_button.xml
new file mode 100644
index 0000000..9a0c8c2
--- /dev/null
+++ b/camerafragment/src/main/res/drawable/take_photo_button.xml
@@ -0,0 +1,18 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/layout/activity_preview.xml b/camerafragment/src/main/res/layout/activity_preview.xml
new file mode 100644
index 0000000..4ee3277
--- /dev/null
+++ b/camerafragment/src/main/res/layout/activity_preview.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/layout/camera_control_panel_layout.xml b/camerafragment/src/main/res/layout/camera_control_panel_layout.xml
new file mode 100644
index 0000000..6ebb786
--- /dev/null
+++ b/camerafragment/src/main/res/layout/camera_control_panel_layout.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/layout/generic_camera_layout.xml b/camerafragment/src/main/res/layout/generic_camera_layout.xml
new file mode 100644
index 0000000..e03940e
--- /dev/null
+++ b/camerafragment/src/main/res/layout/generic_camera_layout.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/values-da/strings.xml b/camerafragment/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..10056cc
--- /dev/null
+++ b/camerafragment/src/main/res/values-da/strings.xml
@@ -0,0 +1,16 @@
+
+
+ Annuller
+ Ok
+
+ Video størrelse %s
+ Billed størrelse
+
+
+ Færdig
+ Gentag
+ Fortryd
+ Beskær
+ Original
+
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/values-sw600dp/configuration.xml b/camerafragment/src/main/res/values-sw600dp/configuration.xml
new file mode 100644
index 0000000..13d442e
--- /dev/null
+++ b/camerafragment/src/main/res/values-sw600dp/configuration.xml
@@ -0,0 +1,6 @@
+
+
+ true
+ 0
+ 0
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/values/configuration.xml b/camerafragment/src/main/res/values/configuration.xml
new file mode 100644
index 0000000..2b14221
--- /dev/null
+++ b/camerafragment/src/main/res/values/configuration.xml
@@ -0,0 +1,6 @@
+
+
+ false
+ 270
+ 90
+
\ No newline at end of file
diff --git a/camerafragment/src/main/res/values/strings.xml b/camerafragment/src/main/res/values/strings.xml
new file mode 100755
index 0000000..9633f3d
--- /dev/null
+++ b/camerafragment/src/main/res/values/strings.xml
@@ -0,0 +1,30 @@
+
+
+ Cancel
+ Ok
+
+ Video size %s
+ Photo size
+
+
+ Save
+ Repeat
+ Cancel
+ Crop
+ Original
+
+
diff --git a/camerafragment/src/main/res/values/styles.xml b/camerafragment/src/main/res/values/styles.xml
new file mode 100755
index 0000000..d95683a
--- /dev/null
+++ b/camerafragment/src/main/res/values/styles.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..aac7c9b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,17 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..04e285f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/media/settings.gif b/media/settings.gif
new file mode 100644
index 0000000..cea118d
Binary files /dev/null and b/media/settings.gif differ
diff --git a/media/settings.mp4 b/media/settings.mp4
new file mode 100644
index 0000000..b31c393
Binary files /dev/null and b/media/settings.mp4 differ
diff --git a/media/switch_action.gif b/media/switch_action.gif
new file mode 100644
index 0000000..2e1305c
Binary files /dev/null and b/media/switch_action.gif differ
diff --git a/media/switch_action.mp4 b/media/switch_action.mp4
new file mode 100644
index 0000000..c2a8fca
Binary files /dev/null and b/media/switch_action.mp4 differ
diff --git a/media/switch_camera.gif b/media/switch_camera.gif
new file mode 100644
index 0000000..bb133f5
Binary files /dev/null and b/media/switch_camera.gif differ
diff --git a/media/switch_camera.mp4 b/media/switch_camera.mp4
new file mode 100644
index 0000000..bc1273d
Binary files /dev/null and b/media/switch_camera.mp4 differ
diff --git a/media/switch_flash.gif b/media/switch_flash.gif
new file mode 100644
index 0000000..a92cd99
Binary files /dev/null and b/media/switch_flash.gif differ
diff --git a/media/switch_flash.mp4 b/media/switch_flash.mp4
new file mode 100644
index 0000000..970ac14
Binary files /dev/null and b/media/switch_flash.mp4 differ
diff --git a/media/takePhoto.mp4 b/media/takePhoto.mp4
new file mode 100644
index 0000000..34903cc
Binary files /dev/null and b/media/takePhoto.mp4 differ
diff --git a/media/take_photo.gif b/media/take_photo.gif
new file mode 100644
index 0000000..16666fd
Binary files /dev/null and b/media/take_photo.gif differ
diff --git a/media/take_photo_2.mp4 b/media/take_photo_2.mp4
new file mode 100644
index 0000000..96f7696
Binary files /dev/null and b/media/take_photo_2.mp4 differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..decdc04
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app', ':camerafragment'