├── settings.gradle
├── screenshots
├── main.png
└── icon-web.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── Application
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ ├── tile.9.png
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_info.png
│ │ ├── drawable-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_info.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_info.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_action_info.png
│ │ ├── values-v21
│ │ │ ├── base-colors.xml
│ │ │ └── base-template-styles.xml
│ │ ├── values
│ │ │ ├── styles.xml
│ │ │ ├── strings.xml
│ │ │ ├── base-strings.xml
│ │ │ ├── template-dimens.xml
│ │ │ └── template-styles.xml
│ │ ├── values-v11
│ │ │ └── template-styles.xml
│ │ ├── values-sw600dp
│ │ │ ├── template-dimens.xml
│ │ │ └── template-styles.xml
│ │ ├── layout
│ │ │ ├── activity_camera.xml
│ │ │ └── fragment_camera2_basic.xml
│ │ └── layout-land
│ │ │ └── fragment_camera2_basic.xml
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── android
│ │ │ └── camera2raw
│ │ │ ├── CameraActivity.java
│ │ │ ├── AutoFitTextureView.java
│ │ │ └── Camera2RawFragment.java
│ │ └── AndroidManifest.xml
├── build.gradle
└── tests
│ ├── AndroidManifest.xml
│ └── src
│ └── com
│ └── example
│ └── android
│ └── camera2raw
│ └── tests
│ └── SampleTests.java
├── README.md
├── packaging.yaml
├── .google
└── packaging.yaml
├── CONTRIBUTING.md
├── CONTRIB.md
├── gradlew.bat
├── gradlew
└── LICENSE
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'Application'
2 |
--------------------------------------------------------------------------------
/screenshots/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/screenshots/main.png
--------------------------------------------------------------------------------
/screenshots/icon-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/screenshots/icon-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-hdpi/tile.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-hdpi/tile.9.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-hdpi/ic_action_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-hdpi/ic_action_info.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-mdpi/ic_action_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-mdpi/ic_action_info.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xhdpi/ic_action_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-xhdpi/ic_action_info.png
--------------------------------------------------------------------------------
/Application/src/main/res/drawable-xxhdpi/ic_action_info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlearchive/android-Camera2Raw/master/Application/src/main/res/drawable-xxhdpi/ic_action_info.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Android Camera2Raw Sample
3 | =========================
4 |
5 | This repo has been migrated to [github.com/android/camera][1]. Please check that repo for future updates. Thank you!
6 |
7 | [1]: https://github.com/android/camera
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
--------------------------------------------------------------------------------
/packaging.yaml:
--------------------------------------------------------------------------------
1 | # GOOGLE SAMPLE PACKAGING DATA
2 | #
3 | # This file is used by Google as part of our samples packaging process.
4 | # End users may safely ignore this file. It has no relevance to other systems.
5 | ---
6 |
7 | status: PUBLISHED
8 | technologies: [Android]
9 | categories: [Media]
10 | languages: [Java]
11 | solutions: [Mobile]
12 | github: googlesamples/android-Camera2Raw
13 | level: BEGINNER
14 | icon: Camera2RawSample/src/main/res/drawable-xxhdpi/ic_launcher.png
15 | license: apache2
16 |
--------------------------------------------------------------------------------
/.google/packaging.yaml:
--------------------------------------------------------------------------------
1 |
2 | # GOOGLE SAMPLE PACKAGING DATA
3 | #
4 | # This file is used by Google as part of our samples packaging process.
5 | # End users may safely ignore this file. It has no relevance to other systems.
6 | ---
7 | status: PUBLISHED
8 | technologies: [Android]
9 | categories: [Media, Camera, Camera2]
10 | languages: [Java]
11 | solutions: [Mobile]
12 | github: android-Camera2Raw
13 | level: INTERMEDIATE
14 | icon: screenshots/icon-web.png
15 | apiRefs:
16 | - android:android.hardware.camera2.DngCreator
17 | license: apache2
18 |
--------------------------------------------------------------------------------
/Application/src/main/res/values-v21/base-colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Application/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Application/src/main/res/values-v11/template-styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Application/src/main/res/values-v21/base-template-styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Application/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | Picture
18 | Info
19 | This app needs camera permission.
20 |
21 |
--------------------------------------------------------------------------------
/Application/src/main/res/values-sw600dp/template-dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | @dimen/margin_huge
22 | @dimen/margin_medium
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Application/src/main/res/values-sw600dp/template-styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Application/src/main/res/layout/activity_camera.xml:
--------------------------------------------------------------------------------
1 |
16 |
23 |
--------------------------------------------------------------------------------
/Application/src/main/res/values/base-strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | Camera2Raw
20 |
21 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Application/src/main/res/values/template-dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | 4dp
22 | 8dp
23 | 16dp
24 | 32dp
25 | 64dp
26 |
27 |
28 |
29 | @dimen/margin_medium
30 | @dimen/margin_medium
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/camera2raw/CameraActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.camera2raw;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 |
22 | /**
23 | * Activity displaying a fragment that implements RAW photo captures.
24 | */
25 | public class CameraActivity extends Activity {
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_camera);
31 | if (null == savedInstanceState) {
32 | getFragmentManager().beginTransaction()
33 | .replace(R.id.container, Camera2RawFragment.newInstance())
34 | .commit();
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Application/src/main/res/values/template-styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Application/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | google()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.0.1'
10 | }
11 | }
12 |
13 | apply plugin: 'com.android.application'
14 |
15 | repositories {
16 | jcenter()
17 | google()
18 | }
19 |
20 | dependencies {
21 | compile "com.android.support:support-v4:27.0.2"
22 | compile "com.android.support:support-v13:27.0.2"
23 | compile "com.android.support:cardview-v7:27.0.2"
24 | compile "com.android.support:appcompat-v7:27.0.2"
25 | }
26 |
27 | // The sample build uses multiple directories to
28 | // keep boilerplate and common code separate from
29 | // the main sample code.
30 | List dirs = [
31 | 'main', // main sample code; look here for the interesting stuff.
32 | 'common', // components that are reused by multiple samples
33 | 'template'] // boilerplate code that is generated by the sample template process
34 |
35 | android {
36 | compileSdkVersion 27
37 |
38 | buildToolsVersion "27.0.2"
39 |
40 | defaultConfig {
41 | minSdkVersion 21
42 | targetSdkVersion 27
43 | }
44 |
45 | compileOptions {
46 | sourceCompatibility JavaVersion.VERSION_1_7
47 | targetCompatibility JavaVersion.VERSION_1_7
48 | }
49 |
50 | sourceSets {
51 | main {
52 | dirs.each { dir ->
53 | java.srcDirs "src/${dir}/java"
54 | res.srcDirs "src/${dir}/res"
55 | }
56 | }
57 | androidTest.setRoot('tests')
58 | androidTest.java.srcDirs = ['tests/src']
59 |
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | ## Contributor License Agreements
4 |
5 | We'd love to accept your sample apps and patches! Before we can take them, we
6 | have to jump a couple of legal hurdles.
7 |
8 | Please fill out either the individual or corporate Contributor License Agreement (CLA).
9 |
10 | * If you are an individual writing original source code and you're sure you
11 | own the intellectual property, then you'll need to sign an [individual CLA]
12 | (https://cla.developers.google.com).
13 | * If you work for a company that wants to allow you to contribute your work,
14 | then you'll need to sign a [corporate CLA]
15 | (https://cla.developers.google.com).
16 |
17 | Follow either of the two links above to access the appropriate CLA and
18 | instructions for how to sign and return it. Once we receive it, we'll be able to
19 | accept your pull requests.
20 |
21 | ## Contributing A Patch
22 |
23 | 1. Submit an issue describing your proposed change to the repo in question.
24 | 1. The repo owner will respond to your issue promptly.
25 | 1. If your proposed change is accepted, and you haven't already done so, sign a
26 | Contributor License Agreement (see details above).
27 | 1. Fork the desired repo, develop and test your code changes.
28 | 1. Ensure that your code adheres to the existing style in the sample to which
29 | you are contributing. Refer to the
30 | [Android Code Style Guide]
31 | (https://source.android.com/source/code-style.html) for the
32 | recommended coding standards for this organization.
33 | 1. Ensure that your code has an appropriate set of unit tests which all pass.
34 | 1. Submit a pull request.
35 |
36 |
--------------------------------------------------------------------------------
/CONTRIB.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | ## Contributor License Agreements
4 |
5 | We'd love to accept your sample apps and patches! Before we can take them, we
6 | have to jump a couple of legal hurdles.
7 |
8 | Please fill out either the individual or corporate Contributor License Agreement (CLA).
9 |
10 | * If you are an individual writing original source code and you're sure you
11 | own the intellectual property, then you'll need to sign an [individual CLA]
12 | (https://developers.google.com/open-source/cla/individual).
13 | * If you work for a company that wants to allow you to contribute your work,
14 | then you'll need to sign a [corporate CLA]
15 | (https://developers.google.com/open-source/cla/corporate).
16 |
17 | Follow either of the two links above to access the appropriate CLA and
18 | instructions for how to sign and return it. Once we receive it, we'll be able to
19 | accept your pull requests.
20 |
21 | ## Contributing A Patch
22 |
23 | 1. Submit an issue describing your proposed change to the repo in question.
24 | 1. The repo owner will respond to your issue promptly.
25 | 1. If your proposed change is accepted, and you haven't already done so, sign a
26 | Contributor License Agreement (see details above).
27 | 1. Fork the desired repo, develop and test your code changes.
28 | 1. Ensure that your code adheres to the existing style in the sample to which
29 | you are contributing. Refer to the
30 | [Android Code Style Guide]
31 | (https://source.android.com/source/code-style.html) for the
32 | recommended coding standards for this organization.
33 | 1. Ensure that your code has an appropriate set of unit tests which all pass.
34 | 1. Submit a pull request.
35 |
36 |
--------------------------------------------------------------------------------
/Application/tests/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
35 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Application/tests/src/com/example/android/camera2raw/tests/SampleTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.android.camera2raw.tests;
17 |
18 | import com.example.android.camera2raw.*;
19 |
20 | import android.test.ActivityInstrumentationTestCase2;
21 |
22 | /**
23 | * Tests for Camera2Raw sample.
24 | */
25 | public class SampleTests extends ActivityInstrumentationTestCase2 {
26 |
27 | private CameraActivity mTestActivity;
28 |
29 | public SampleTests() {
30 | super(CameraActivity.class);
31 | }
32 |
33 | @Override
34 | protected void setUp() throws Exception {
35 | super.setUp();
36 |
37 | // Starts the activity under test using the default Intent with:
38 | // action = {@link Intent#ACTION_MAIN}
39 | // flags = {@link Intent#FLAG_ACTIVITY_NEW_TASK}
40 | // All other fields are null or empty.
41 | mTestActivity = getActivity();
42 | }
43 |
44 | /**
45 | * Test if the test fixture has been set up correctly.
46 | */
47 | public void testPreconditions() {
48 | //Try to add a message to add context to your assertions. These messages will be shown if
49 | //a tests fails and make it easy to understand why a test failed
50 | assertNotNull("mTestActivity is null", mTestActivity);
51 | }
52 |
53 | /**
54 | * Add more tests below.
55 | */
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Application/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/Application/src/main/res/layout/fragment_camera2_basic.xml:
--------------------------------------------------------------------------------
1 |
16 |
19 |
20 |
26 |
27 |
34 |
35 |
41 |
42 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Application/src/main/res/layout-land/fragment_camera2_basic.xml:
--------------------------------------------------------------------------------
1 |
16 |
19 |
20 |
27 |
28 |
38 |
39 |
45 |
46 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/camera2raw/AutoFitTextureView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.camera2raw;
18 |
19 | import android.content.Context;
20 | import android.util.AttributeSet;
21 | import android.view.TextureView;
22 |
23 | /**
24 | * A {@link TextureView} that can be adjusted to a specified aspect ratio.
25 | */
26 | public class AutoFitTextureView extends TextureView {
27 |
28 | private int mRatioWidth = 0;
29 | private int mRatioHeight = 0;
30 |
31 | public AutoFitTextureView(Context context) {
32 | this(context, null);
33 | }
34 |
35 | public AutoFitTextureView(Context context, AttributeSet attrs) {
36 | this(context, attrs, 0);
37 | }
38 |
39 | public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
40 | super(context, attrs, defStyle);
41 | }
42 |
43 | /**
44 | * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
45 | * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
46 | * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
47 | *
48 | * @param width Relative horizontal size
49 | * @param height Relative vertical size
50 | */
51 | public void setAspectRatio(int width, int height) {
52 | if (width < 0 || height < 0) {
53 | throw new IllegalArgumentException("Size cannot be negative.");
54 | }
55 | if (mRatioWidth == width && mRatioHeight == height) {
56 | return;
57 | }
58 | mRatioWidth = width;
59 | mRatioHeight = height;
60 | requestLayout();
61 | }
62 |
63 | @Override
64 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
65 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
66 | int width = MeasureSpec.getSize(widthMeasureSpec);
67 | int height = MeasureSpec.getSize(heightMeasureSpec);
68 | if (0 == mRatioWidth || 0 == mRatioHeight) {
69 | setMeasuredDimension(width, height);
70 | } else {
71 | if (width < height * mRatioWidth / mRatioHeight) {
72 | setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
73 | } else {
74 | setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
75 | }
76 | }
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | --------------
3 |
4 | Version 2.0, January 2004
5 | http://www.apache.org/licenses/
6 |
7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8 |
9 | 1. Definitions.
10 |
11 | "License" shall mean the terms and conditions for use, reproduction,
12 | and distribution as defined by Sections 1 through 9 of this document.
13 |
14 | "Licensor" shall mean the copyright owner or entity authorized by
15 | the copyright owner that is granting the License.
16 |
17 | "Legal Entity" shall mean the union of the acting entity and all
18 | other entities that control, are controlled by, or are under common
19 | control with that entity. For the purposes of this definition,
20 | "control" means (i) the power, direct or indirect, to cause the
21 | direction or management of such entity, whether by contract or
22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
23 | outstanding shares, or (iii) beneficial ownership of such entity.
24 |
25 | "You" (or "Your") shall mean an individual or Legal Entity
26 | exercising permissions granted by this License.
27 |
28 | "Source" form shall mean the preferred form for making modifications,
29 | including but not limited to software source code, documentation
30 | source, and configuration files.
31 |
32 | "Object" form shall mean any form resulting from mechanical
33 | transformation or translation of a Source form, including but
34 | not limited to compiled object code, generated documentation,
35 | and conversions to other media types.
36 |
37 | "Work" shall mean the work of authorship, whether in Source or
38 | Object form, made available under the License, as indicated by a
39 | copyright notice that is included in or attached to the work
40 | (an example is provided in the Appendix below).
41 |
42 | "Derivative Works" shall mean any work, whether in Source or Object
43 | form, that is based on (or derived from) the Work and for which the
44 | editorial revisions, annotations, elaborations, or other modifications
45 | represent, as a whole, an original work of authorship. For the purposes
46 | of this License, Derivative Works shall not include works that remain
47 | separable from, or merely link (or bind by name) to the interfaces of,
48 | the Work and Derivative Works thereof.
49 |
50 | "Contribution" shall mean any work of authorship, including
51 | the original version of the Work and any modifications or additions
52 | to that Work or Derivative Works thereof, that is intentionally
53 | submitted to Licensor for inclusion in the Work by the copyright owner
54 | or by an individual or Legal Entity authorized to submit on behalf of
55 | the copyright owner. For the purposes of this definition, "submitted"
56 | means any form of electronic, verbal, or written communication sent
57 | to the Licensor or its representatives, including but not limited to
58 | communication on electronic mailing lists, source code control systems,
59 | and issue tracking systems that are managed by, or on behalf of, the
60 | Licensor for the purpose of discussing and improving the Work, but
61 | excluding communication that is conspicuously marked or otherwise
62 | designated in writing by the copyright owner as "Not a Contribution."
63 |
64 | "Contributor" shall mean Licensor and any individual or Legal Entity
65 | on behalf of whom a Contribution has been received by Licensor and
66 | subsequently incorporated within the Work.
67 |
68 | 2. Grant of Copyright License. Subject to the terms and conditions of
69 | this License, each Contributor hereby grants to You a perpetual,
70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71 | copyright license to reproduce, prepare Derivative Works of,
72 | publicly display, publicly perform, sublicense, and distribute the
73 | Work and such Derivative Works in Source or Object form.
74 |
75 | 3. Grant of Patent License. Subject to the terms and conditions of
76 | this License, each Contributor hereby grants to You a perpetual,
77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78 | (except as stated in this section) patent license to make, have made,
79 | use, offer to sell, sell, import, and otherwise transfer the Work,
80 | where such license applies only to those patent claims licensable
81 | by such Contributor that are necessarily infringed by their
82 | Contribution(s) alone or by combination of their Contribution(s)
83 | with the Work to which such Contribution(s) was submitted. If You
84 | institute patent litigation against any entity (including a
85 | cross-claim or counterclaim in a lawsuit) alleging that the Work
86 | or a Contribution incorporated within the Work constitutes direct
87 | or contributory patent infringement, then any patent licenses
88 | granted to You under this License for that Work shall terminate
89 | as of the date such litigation is filed.
90 |
91 | 4. Redistribution. You may reproduce and distribute copies of the
92 | Work or Derivative Works thereof in any medium, with or without
93 | modifications, and in Source or Object form, provided that You
94 | meet the following conditions:
95 |
96 | (a) You must give any other recipients of the Work or
97 | Derivative Works a copy of this License; and
98 |
99 | (b) You must cause any modified files to carry prominent notices
100 | stating that You changed the files; and
101 |
102 | (c) You must retain, in the Source form of any Derivative Works
103 | that You distribute, all copyright, patent, trademark, and
104 | attribution notices from the Source form of the Work,
105 | excluding those notices that do not pertain to any part of
106 | the Derivative Works; and
107 |
108 | (d) If the Work includes a "NOTICE" text file as part of its
109 | distribution, then any Derivative Works that You distribute must
110 | include a readable copy of the attribution notices contained
111 | within such NOTICE file, excluding those notices that do not
112 | pertain to any part of the Derivative Works, in at least one
113 | of the following places: within a NOTICE text file distributed
114 | as part of the Derivative Works; within the Source form or
115 | documentation, if provided along with the Derivative Works; or,
116 | within a display generated by the Derivative Works, if and
117 | wherever such third-party notices normally appear. The contents
118 | of the NOTICE file are for informational purposes only and
119 | do not modify the License. You may add Your own attribution
120 | notices within Derivative Works that You distribute, alongside
121 | or as an addendum to the NOTICE text from the Work, provided
122 | that such additional attribution notices cannot be construed
123 | as modifying the License.
124 |
125 | You may add Your own copyright statement to Your modifications and
126 | may provide additional or different license terms and conditions
127 | for use, reproduction, or distribution of Your modifications, or
128 | for any such Derivative Works as a whole, provided Your use,
129 | reproduction, and distribution of the Work otherwise complies with
130 | the conditions stated in this License.
131 |
132 | 5. Submission of Contributions. Unless You explicitly state otherwise,
133 | any Contribution intentionally submitted for inclusion in the Work
134 | by You to the Licensor shall be under the terms and conditions of
135 | this License, without any additional terms or conditions.
136 | Notwithstanding the above, nothing herein shall supersede or modify
137 | the terms of any separate license agreement you may have executed
138 | with Licensor regarding such Contributions.
139 |
140 | 6. Trademarks. This License does not grant permission to use the trade
141 | names, trademarks, service marks, or product names of the Licensor,
142 | except as required for reasonable and customary use in describing the
143 | origin of the Work and reproducing the content of the NOTICE file.
144 |
145 | 7. Disclaimer of Warranty. Unless required by applicable law or
146 | agreed to in writing, Licensor provides the Work (and each
147 | Contributor provides its Contributions) on an "AS IS" BASIS,
148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149 | implied, including, without limitation, any warranties or conditions
150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151 | PARTICULAR PURPOSE. You are solely responsible for determining the
152 | appropriateness of using or redistributing the Work and assume any
153 | risks associated with Your exercise of permissions under this License.
154 |
155 | 8. Limitation of Liability. In no event and under no legal theory,
156 | whether in tort (including negligence), contract, or otherwise,
157 | unless required by applicable law (such as deliberate and grossly
158 | negligent acts) or agreed to in writing, shall any Contributor be
159 | liable to You for damages, including any direct, indirect, special,
160 | incidental, or consequential damages of any character arising as a
161 | result of this License or out of the use or inability to use the
162 | Work (including but not limited to damages for loss of goodwill,
163 | work stoppage, computer failure or malfunction, or any and all
164 | other commercial damages or losses), even if such Contributor
165 | has been advised of the possibility of such damages.
166 |
167 | 9. Accepting Warranty or Additional Liability. While redistributing
168 | the Work or Derivative Works thereof, You may choose to offer,
169 | and charge a fee for, acceptance of support, warranty, indemnity,
170 | or other liability obligations and/or rights consistent with this
171 | License. However, in accepting such obligations, You may act only
172 | on Your own behalf and on Your sole responsibility, not on behalf
173 | of any other Contributor, and only if You agree to indemnify,
174 | defend, and hold each Contributor harmless for any liability
175 | incurred by, or claims asserted against, such Contributor by reason
176 | of your accepting any such warranty or additional liability.
177 |
178 | END OF TERMS AND CONDITIONS
179 |
180 | APPENDIX: How to apply the Apache License to your work.
181 |
182 | To apply the Apache License to your work, attach the following
183 | boilerplate notice, with the fields enclosed by brackets "{}"
184 | replaced with your own identifying information. (Don't include
185 | the brackets!) The text should be enclosed in the appropriate
186 | comment syntax for the file format. We also recommend that a
187 | file or class name and description of purpose be included on the
188 | same "printed page" as the copyright notice for easier
189 | identification within third-party archives.
190 |
191 | Copyright {yyyy} {name of copyright owner}
192 |
193 | Licensed under the Apache License, Version 2.0 (the "License");
194 | you may not use this file except in compliance with the License.
195 | You may obtain a copy of the License at
196 |
197 | http://www.apache.org/licenses/LICENSE-2.0
198 |
199 | Unless required by applicable law or agreed to in writing, software
200 | distributed under the License is distributed on an "AS IS" BASIS,
201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202 | See the License for the specific language governing permissions and
203 | limitations under the License.
204 |
--------------------------------------------------------------------------------
/Application/src/main/java/com/example/android/camera2raw/Camera2RawFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.android.camera2raw;
18 |
19 | import android.Manifest;
20 | import android.app.Activity;
21 | import android.app.AlertDialog;
22 | import android.app.Dialog;
23 | import android.app.DialogFragment;
24 | import android.app.Fragment;
25 | import android.content.Context;
26 | import android.content.DialogInterface;
27 | import android.content.pm.PackageManager;
28 | import android.graphics.ImageFormat;
29 | import android.graphics.Matrix;
30 | import android.graphics.Point;
31 | import android.graphics.RectF;
32 | import android.graphics.SurfaceTexture;
33 | import android.hardware.SensorManager;
34 | import android.hardware.camera2.CameraAccessException;
35 | import android.hardware.camera2.CameraCaptureSession;
36 | import android.hardware.camera2.CameraCharacteristics;
37 | import android.hardware.camera2.CameraDevice;
38 | import android.hardware.camera2.CameraManager;
39 | import android.hardware.camera2.CameraMetadata;
40 | import android.hardware.camera2.CaptureFailure;
41 | import android.hardware.camera2.CaptureRequest;
42 | import android.hardware.camera2.CaptureResult;
43 | import android.hardware.camera2.DngCreator;
44 | import android.hardware.camera2.TotalCaptureResult;
45 | import android.hardware.camera2.params.StreamConfigurationMap;
46 | import android.media.Image;
47 | import android.media.ImageReader;
48 | import android.media.MediaScannerConnection;
49 | import android.net.Uri;
50 | import android.os.AsyncTask;
51 | import android.os.Bundle;
52 | import android.os.Environment;
53 | import android.os.Handler;
54 | import android.os.HandlerThread;
55 | import android.os.Looper;
56 | import android.os.Message;
57 | import android.os.SystemClock;
58 | import android.support.v13.app.FragmentCompat;
59 | import android.support.v4.app.ActivityCompat;
60 | import android.util.Log;
61 | import android.util.Size;
62 | import android.util.SparseIntArray;
63 | import android.view.LayoutInflater;
64 | import android.view.OrientationEventListener;
65 | import android.view.Surface;
66 | import android.view.TextureView;
67 | import android.view.View;
68 | import android.view.ViewGroup;
69 | import android.widget.Toast;
70 |
71 | import java.io.File;
72 | import java.io.FileOutputStream;
73 | import java.io.IOException;
74 | import java.io.OutputStream;
75 | import java.nio.ByteBuffer;
76 | import java.text.SimpleDateFormat;
77 | import java.util.ArrayList;
78 | import java.util.Arrays;
79 | import java.util.Collections;
80 | import java.util.Comparator;
81 | import java.util.Date;
82 | import java.util.List;
83 | import java.util.Locale;
84 | import java.util.Map;
85 | import java.util.TreeMap;
86 | import java.util.concurrent.Semaphore;
87 | import java.util.concurrent.TimeUnit;
88 | import java.util.concurrent.atomic.AtomicInteger;
89 |
90 | /**
91 | * A fragment that demonstrates use of the Camera2 API to capture RAW and JPEG photos.
92 | *
93 | * In this example, the lifecycle of a single request to take a photo is:
94 | *
95 | *
96 | * The user presses the "Picture" button, resulting in a call to {@link #takePicture()}.
97 | *
98 | *
99 | * {@link #takePicture()} initiates a pre-capture sequence that triggers the camera's built-in
100 | * auto-focus, auto-exposure, and auto-white-balance algorithms (aka. "3A") to run.
101 | *
102 | *
103 | * When the pre-capture sequence has finished, a {@link CaptureRequest} with a monotonically
104 | * increasing request ID set by calls to {@link CaptureRequest.Builder#setTag(Object)} is sent to
105 | * the camera to begin the JPEG and RAW capture sequence, and an
106 | * {@link ImageSaver.ImageSaverBuilder} is stored for this request in the
107 | * {@link #mJpegResultQueue} and {@link #mRawResultQueue}.
108 | *
109 | *
110 | * As {@link CaptureResult}s and {@link Image}s become available via callbacks in a background
111 | * thread, a {@link ImageSaver.ImageSaverBuilder} is looked up by the request ID in
112 | * {@link #mJpegResultQueue} and {@link #mRawResultQueue} and updated.
113 | *
114 | *
115 | * When all of the necessary results to save an image are available, the an {@link ImageSaver} is
116 | * constructed by the {@link ImageSaver.ImageSaverBuilder} and passed to a separate background
117 | * thread to save to a file.
118 | *
119 | *
120 | */
121 | public class Camera2RawFragment extends Fragment
122 | implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback {
123 |
124 | /**
125 | * Conversion from screen rotation to JPEG orientation.
126 | */
127 | private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
128 |
129 | static {
130 | ORIENTATIONS.append(Surface.ROTATION_0, 0);
131 | ORIENTATIONS.append(Surface.ROTATION_90, 90);
132 | ORIENTATIONS.append(Surface.ROTATION_180, 180);
133 | ORIENTATIONS.append(Surface.ROTATION_270, 270);
134 | }
135 |
136 | /**
137 | * Request code for camera permissions.
138 | */
139 | private static final int REQUEST_CAMERA_PERMISSIONS = 1;
140 |
141 | /**
142 | * Permissions required to take a picture.
143 | */
144 | private static final String[] CAMERA_PERMISSIONS = {
145 | Manifest.permission.CAMERA,
146 | Manifest.permission.READ_EXTERNAL_STORAGE,
147 | Manifest.permission.WRITE_EXTERNAL_STORAGE,
148 | };
149 |
150 | /**
151 | * Timeout for the pre-capture sequence.
152 | */
153 | private static final long PRECAPTURE_TIMEOUT_MS = 1000;
154 |
155 | /**
156 | * Tolerance when comparing aspect ratios.
157 | */
158 | private static final double ASPECT_RATIO_TOLERANCE = 0.005;
159 |
160 | /**
161 | * Max preview width that is guaranteed by Camera2 API
162 | */
163 | private static final int MAX_PREVIEW_WIDTH = 1920;
164 |
165 | /**
166 | * Max preview height that is guaranteed by Camera2 API
167 | */
168 | private static final int MAX_PREVIEW_HEIGHT = 1080;
169 |
170 | /**
171 | * Tag for the {@link Log}.
172 | */
173 | private static final String TAG = "Camera2RawFragment";
174 |
175 | /**
176 | * Camera state: Device is closed.
177 | */
178 | private static final int STATE_CLOSED = 0;
179 |
180 | /**
181 | * Camera state: Device is opened, but is not capturing.
182 | */
183 | private static final int STATE_OPENED = 1;
184 |
185 | /**
186 | * Camera state: Showing camera preview.
187 | */
188 | private static final int STATE_PREVIEW = 2;
189 |
190 | /**
191 | * Camera state: Waiting for 3A convergence before capturing a photo.
192 | */
193 | private static final int STATE_WAITING_FOR_3A_CONVERGENCE = 3;
194 |
195 | /**
196 | * An {@link OrientationEventListener} used to determine when device rotation has occurred.
197 | * This is mainly necessary for when the device is rotated by 180 degrees, in which case
198 | * onCreate or onConfigurationChanged is not called as the view dimensions remain the same,
199 | * but the orientation of the has changed, and thus the preview rotation must be updated.
200 | */
201 | private OrientationEventListener mOrientationListener;
202 |
203 | /**
204 | * {@link TextureView.SurfaceTextureListener} handles several lifecycle events of a
205 | * {@link TextureView}.
206 | */
207 | private final TextureView.SurfaceTextureListener mSurfaceTextureListener
208 | = new TextureView.SurfaceTextureListener() {
209 |
210 | @Override
211 | public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
212 | configureTransform(width, height);
213 | }
214 |
215 | @Override
216 | public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
217 | configureTransform(width, height);
218 | }
219 |
220 | @Override
221 | public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
222 | synchronized (mCameraStateLock) {
223 | mPreviewSize = null;
224 | }
225 | return true;
226 | }
227 |
228 | @Override
229 | public void onSurfaceTextureUpdated(SurfaceTexture texture) {
230 | }
231 |
232 | };
233 |
234 | /**
235 | * An {@link AutoFitTextureView} for camera preview.
236 | */
237 | private AutoFitTextureView mTextureView;
238 |
239 | /**
240 | * An additional thread for running tasks that shouldn't block the UI. This is used for all
241 | * callbacks from the {@link CameraDevice} and {@link CameraCaptureSession}s.
242 | */
243 | private HandlerThread mBackgroundThread;
244 |
245 | /**
246 | * A counter for tracking corresponding {@link CaptureRequest}s and {@link CaptureResult}s
247 | * across the {@link CameraCaptureSession} capture callbacks.
248 | */
249 | private final AtomicInteger mRequestCounter = new AtomicInteger();
250 |
251 | /**
252 | * A {@link Semaphore} to prevent the app from exiting before closing the camera.
253 | */
254 | private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
255 |
256 | /**
257 | * A lock protecting camera state.
258 | */
259 | private final Object mCameraStateLock = new Object();
260 |
261 | // *********************************************************************************************
262 | // State protected by mCameraStateLock.
263 | //
264 | // The following state is used across both the UI and background threads. Methods with "Locked"
265 | // in the name expect mCameraStateLock to be held while calling.
266 |
267 | /**
268 | * ID of the current {@link CameraDevice}.
269 | */
270 | private String mCameraId;
271 |
272 | /**
273 | * A {@link CameraCaptureSession } for camera preview.
274 | */
275 | private CameraCaptureSession mCaptureSession;
276 |
277 | /**
278 | * A reference to the open {@link CameraDevice}.
279 | */
280 | private CameraDevice mCameraDevice;
281 |
282 | /**
283 | * The {@link Size} of camera preview.
284 | */
285 | private Size mPreviewSize;
286 |
287 | /**
288 | * The {@link CameraCharacteristics} for the currently configured camera device.
289 | */
290 | private CameraCharacteristics mCharacteristics;
291 |
292 | /**
293 | * A {@link Handler} for running tasks in the background.
294 | */
295 | private Handler mBackgroundHandler;
296 |
297 | /**
298 | * A reference counted holder wrapping the {@link ImageReader} that handles JPEG image
299 | * captures. This is used to allow us to clean up the {@link ImageReader} when all background
300 | * tasks using its {@link Image}s have completed.
301 | */
302 | private RefCountedAutoCloseable mJpegImageReader;
303 |
304 | /**
305 | * A reference counted holder wrapping the {@link ImageReader} that handles RAW image captures.
306 | * This is used to allow us to clean up the {@link ImageReader} when all background tasks using
307 | * its {@link Image}s have completed.
308 | */
309 | private RefCountedAutoCloseable mRawImageReader;
310 |
311 | /**
312 | * Whether or not the currently configured camera device is fixed-focus.
313 | */
314 | private boolean mNoAFRun = false;
315 |
316 | /**
317 | * Number of pending user requests to capture a photo.
318 | */
319 | private int mPendingUserCaptures = 0;
320 |
321 | /**
322 | * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress JPEG captures.
323 | */
324 | private final TreeMap mJpegResultQueue = new TreeMap<>();
325 |
326 | /**
327 | * Request ID to {@link ImageSaver.ImageSaverBuilder} mapping for in-progress RAW captures.
328 | */
329 | private final TreeMap mRawResultQueue = new TreeMap<>();
330 |
331 | /**
332 | * {@link CaptureRequest.Builder} for the camera preview
333 | */
334 | private CaptureRequest.Builder mPreviewRequestBuilder;
335 |
336 | /**
337 | * The state of the camera device.
338 | *
339 | * @see #mPreCaptureCallback
340 | */
341 | private int mState = STATE_CLOSED;
342 |
343 | /**
344 | * Timer to use with pre-capture sequence to ensure a timely capture if 3A convergence is
345 | * taking too long.
346 | */
347 | private long mCaptureTimer;
348 |
349 | //**********************************************************************************************
350 |
351 | /**
352 | * {@link CameraDevice.StateCallback} is called when the currently active {@link CameraDevice}
353 | * changes its state.
354 | */
355 | private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
356 |
357 | @Override
358 | public void onOpened(CameraDevice cameraDevice) {
359 | // This method is called when the camera is opened. We start camera preview here if
360 | // the TextureView displaying this has been set up.
361 | synchronized (mCameraStateLock) {
362 | mState = STATE_OPENED;
363 | mCameraOpenCloseLock.release();
364 | mCameraDevice = cameraDevice;
365 |
366 | // Start the preview session if the TextureView has been set up already.
367 | if (mPreviewSize != null && mTextureView.isAvailable()) {
368 | createCameraPreviewSessionLocked();
369 | }
370 | }
371 | }
372 |
373 | @Override
374 | public void onDisconnected(CameraDevice cameraDevice) {
375 | synchronized (mCameraStateLock) {
376 | mState = STATE_CLOSED;
377 | mCameraOpenCloseLock.release();
378 | cameraDevice.close();
379 | mCameraDevice = null;
380 | }
381 | }
382 |
383 | @Override
384 | public void onError(CameraDevice cameraDevice, int error) {
385 | Log.e(TAG, "Received camera device error: " + error);
386 | synchronized (mCameraStateLock) {
387 | mState = STATE_CLOSED;
388 | mCameraOpenCloseLock.release();
389 | cameraDevice.close();
390 | mCameraDevice = null;
391 | }
392 | Activity activity = getActivity();
393 | if (null != activity) {
394 | activity.finish();
395 | }
396 | }
397 |
398 | };
399 |
400 | /**
401 | * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
402 | * JPEG image is ready to be saved.
403 | */
404 | private final ImageReader.OnImageAvailableListener mOnJpegImageAvailableListener
405 | = new ImageReader.OnImageAvailableListener() {
406 |
407 | @Override
408 | public void onImageAvailable(ImageReader reader) {
409 | dequeueAndSaveImage(mJpegResultQueue, mJpegImageReader);
410 | }
411 |
412 | };
413 |
414 | /**
415 | * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
416 | * RAW image is ready to be saved.
417 | */
418 | private final ImageReader.OnImageAvailableListener mOnRawImageAvailableListener
419 | = new ImageReader.OnImageAvailableListener() {
420 |
421 | @Override
422 | public void onImageAvailable(ImageReader reader) {
423 | dequeueAndSaveImage(mRawResultQueue, mRawImageReader);
424 | }
425 |
426 | };
427 |
428 | /**
429 | * A {@link CameraCaptureSession.CaptureCallback} that handles events for the preview and
430 | * pre-capture sequence.
431 | */
432 | private CameraCaptureSession.CaptureCallback mPreCaptureCallback
433 | = new CameraCaptureSession.CaptureCallback() {
434 |
435 | private void process(CaptureResult result) {
436 | synchronized (mCameraStateLock) {
437 | switch (mState) {
438 | case STATE_PREVIEW: {
439 | // We have nothing to do when the camera preview is running normally.
440 | break;
441 | }
442 | case STATE_WAITING_FOR_3A_CONVERGENCE: {
443 | boolean readyToCapture = true;
444 | if (!mNoAFRun) {
445 | Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
446 | if (afState == null) {
447 | break;
448 | }
449 |
450 | // If auto-focus has reached locked state, we are ready to capture
451 | readyToCapture =
452 | (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
453 | afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED);
454 | }
455 |
456 | // If we are running on an non-legacy device, we should also wait until
457 | // auto-exposure and auto-white-balance have converged as well before
458 | // taking a picture.
459 | if (!isLegacyLocked()) {
460 | Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
461 | Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
462 | if (aeState == null || awbState == null) {
463 | break;
464 | }
465 |
466 | readyToCapture = readyToCapture &&
467 | aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED &&
468 | awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED;
469 | }
470 |
471 | // If we haven't finished the pre-capture sequence but have hit our maximum
472 | // wait timeout, too bad! Begin capture anyway.
473 | if (!readyToCapture && hitTimeoutLocked()) {
474 | Log.w(TAG, "Timed out waiting for pre-capture sequence to complete.");
475 | readyToCapture = true;
476 | }
477 |
478 | if (readyToCapture && mPendingUserCaptures > 0) {
479 | // Capture once for each user tap of the "Picture" button.
480 | while (mPendingUserCaptures > 0) {
481 | captureStillPictureLocked();
482 | mPendingUserCaptures--;
483 | }
484 | // After this, the camera will go back to the normal state of preview.
485 | mState = STATE_PREVIEW;
486 | }
487 | }
488 | }
489 | }
490 | }
491 |
492 | @Override
493 | public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
494 | CaptureResult partialResult) {
495 | process(partialResult);
496 | }
497 |
498 | @Override
499 | public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
500 | TotalCaptureResult result) {
501 | process(result);
502 | }
503 |
504 | };
505 |
506 | /**
507 | * A {@link CameraCaptureSession.CaptureCallback} that handles the still JPEG and RAW capture
508 | * request.
509 | */
510 | private final CameraCaptureSession.CaptureCallback mCaptureCallback
511 | = new CameraCaptureSession.CaptureCallback() {
512 | @Override
513 | public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
514 | long timestamp, long frameNumber) {
515 | String currentDateTime = generateTimestamp();
516 | File rawFile = new File(Environment.
517 | getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
518 | "RAW_" + currentDateTime + ".dng");
519 | File jpegFile = new File(Environment.
520 | getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
521 | "JPEG_" + currentDateTime + ".jpg");
522 |
523 | // Look up the ImageSaverBuilder for this request and update it with the file name
524 | // based on the capture start time.
525 | ImageSaver.ImageSaverBuilder jpegBuilder;
526 | ImageSaver.ImageSaverBuilder rawBuilder;
527 | int requestId = (int) request.getTag();
528 | synchronized (mCameraStateLock) {
529 | jpegBuilder = mJpegResultQueue.get(requestId);
530 | rawBuilder = mRawResultQueue.get(requestId);
531 | }
532 |
533 | if (jpegBuilder != null) jpegBuilder.setFile(jpegFile);
534 | if (rawBuilder != null) rawBuilder.setFile(rawFile);
535 | }
536 |
537 | @Override
538 | public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
539 | TotalCaptureResult result) {
540 | int requestId = (int) request.getTag();
541 | ImageSaver.ImageSaverBuilder jpegBuilder;
542 | ImageSaver.ImageSaverBuilder rawBuilder;
543 | StringBuilder sb = new StringBuilder();
544 |
545 | // Look up the ImageSaverBuilder for this request and update it with the CaptureResult
546 | synchronized (mCameraStateLock) {
547 | jpegBuilder = mJpegResultQueue.get(requestId);
548 | rawBuilder = mRawResultQueue.get(requestId);
549 |
550 | if (jpegBuilder != null) {
551 | jpegBuilder.setResult(result);
552 | sb.append("Saving JPEG as: ");
553 | sb.append(jpegBuilder.getSaveLocation());
554 | }
555 | if (rawBuilder != null) {
556 | rawBuilder.setResult(result);
557 | if (jpegBuilder != null) sb.append(", ");
558 | sb.append("Saving RAW as: ");
559 | sb.append(rawBuilder.getSaveLocation());
560 | }
561 |
562 | // If we have all the results necessary, save the image to a file in the background.
563 | handleCompletionLocked(requestId, jpegBuilder, mJpegResultQueue);
564 | handleCompletionLocked(requestId, rawBuilder, mRawResultQueue);
565 |
566 | finishedCaptureLocked();
567 | }
568 |
569 | showToast(sb.toString());
570 | }
571 |
572 | @Override
573 | public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
574 | CaptureFailure failure) {
575 | int requestId = (int) request.getTag();
576 | synchronized (mCameraStateLock) {
577 | mJpegResultQueue.remove(requestId);
578 | mRawResultQueue.remove(requestId);
579 | finishedCaptureLocked();
580 | }
581 | showToast("Capture failed!");
582 | }
583 |
584 | };
585 |
586 | /**
587 | * A {@link Handler} for showing {@link Toast}s on the UI thread.
588 | */
589 | private final Handler mMessageHandler = new Handler(Looper.getMainLooper()) {
590 | @Override
591 | public void handleMessage(Message msg) {
592 | Activity activity = getActivity();
593 | if (activity != null) {
594 | Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show();
595 | }
596 | }
597 | };
598 |
599 | public static Camera2RawFragment newInstance() {
600 | return new Camera2RawFragment();
601 | }
602 |
603 | @Override
604 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
605 | Bundle savedInstanceState) {
606 | return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
607 | }
608 |
609 | @Override
610 | public void onViewCreated(final View view, Bundle savedInstanceState) {
611 | view.findViewById(R.id.picture).setOnClickListener(this);
612 | view.findViewById(R.id.info).setOnClickListener(this);
613 | mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
614 |
615 | // Setup a new OrientationEventListener. This is used to handle rotation events like a
616 | // 180 degree rotation that do not normally trigger a call to onCreate to do view re-layout
617 | // or otherwise cause the preview TextureView's size to change.
618 | mOrientationListener = new OrientationEventListener(getActivity(),
619 | SensorManager.SENSOR_DELAY_NORMAL) {
620 | @Override
621 | public void onOrientationChanged(int orientation) {
622 | if (mTextureView != null && mTextureView.isAvailable()) {
623 | configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
624 | }
625 | }
626 | };
627 | }
628 |
629 | @Override
630 | public void onResume() {
631 | super.onResume();
632 | startBackgroundThread();
633 | openCamera();
634 |
635 | // When the screen is turned off and turned back on, the SurfaceTexture is already
636 | // available, and "onSurfaceTextureAvailable" will not be called. In that case, we should
637 | // configure the preview bounds here (otherwise, we wait until the surface is ready in
638 | // the SurfaceTextureListener).
639 | if (mTextureView.isAvailable()) {
640 | configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
641 | } else {
642 | mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
643 | }
644 | if (mOrientationListener != null && mOrientationListener.canDetectOrientation()) {
645 | mOrientationListener.enable();
646 | }
647 | }
648 |
649 | @Override
650 | public void onPause() {
651 | if (mOrientationListener != null) {
652 | mOrientationListener.disable();
653 | }
654 | closeCamera();
655 | stopBackgroundThread();
656 | super.onPause();
657 | }
658 |
659 | @Override
660 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
661 | if (requestCode == REQUEST_CAMERA_PERMISSIONS) {
662 | for (int result : grantResults) {
663 | if (result != PackageManager.PERMISSION_GRANTED) {
664 | showMissingPermissionError();
665 | return;
666 | }
667 | }
668 | } else {
669 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
670 | }
671 | }
672 |
673 | @Override
674 | public void onClick(View view) {
675 | switch (view.getId()) {
676 | case R.id.picture: {
677 | takePicture();
678 | break;
679 | }
680 | case R.id.info: {
681 | Activity activity = getActivity();
682 | if (null != activity) {
683 | new AlertDialog.Builder(activity)
684 | .setMessage(R.string.intro_message)
685 | .setPositiveButton(android.R.string.ok, null)
686 | .show();
687 | }
688 | break;
689 | }
690 | }
691 | }
692 |
693 | /**
694 | * Sets up state related to camera that is needed before opening a {@link CameraDevice}.
695 | */
696 | private boolean setUpCameraOutputs() {
697 | Activity activity = getActivity();
698 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
699 | if (manager == null) {
700 | ErrorDialog.buildErrorDialog("This device doesn't support Camera2 API.").
701 | show(getFragmentManager(), "dialog");
702 | return false;
703 | }
704 | try {
705 | // Find a CameraDevice that supports RAW captures, and configure state.
706 | for (String cameraId : manager.getCameraIdList()) {
707 | CameraCharacteristics characteristics
708 | = manager.getCameraCharacteristics(cameraId);
709 |
710 | // We only use a camera that supports RAW in this sample.
711 | if (!contains(characteristics.get(
712 | CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES),
713 | CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
714 | continue;
715 | }
716 |
717 | StreamConfigurationMap map = characteristics.get(
718 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
719 |
720 | // For still image captures, we use the largest available size.
721 | Size largestJpeg = Collections.max(
722 | Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
723 | new CompareSizesByArea());
724 |
725 | Size largestRaw = Collections.max(
726 | Arrays.asList(map.getOutputSizes(ImageFormat.RAW_SENSOR)),
727 | new CompareSizesByArea());
728 |
729 | synchronized (mCameraStateLock) {
730 | // Set up ImageReaders for JPEG and RAW outputs. Place these in a reference
731 | // counted wrapper to ensure they are only closed when all background tasks
732 | // using them are finished.
733 | if (mJpegImageReader == null || mJpegImageReader.getAndRetain() == null) {
734 | mJpegImageReader = new RefCountedAutoCloseable<>(
735 | ImageReader.newInstance(largestJpeg.getWidth(),
736 | largestJpeg.getHeight(), ImageFormat.JPEG, /*maxImages*/5));
737 | }
738 | mJpegImageReader.get().setOnImageAvailableListener(
739 | mOnJpegImageAvailableListener, mBackgroundHandler);
740 |
741 | if (mRawImageReader == null || mRawImageReader.getAndRetain() == null) {
742 | mRawImageReader = new RefCountedAutoCloseable<>(
743 | ImageReader.newInstance(largestRaw.getWidth(),
744 | largestRaw.getHeight(), ImageFormat.RAW_SENSOR, /*maxImages*/ 5));
745 | }
746 | mRawImageReader.get().setOnImageAvailableListener(
747 | mOnRawImageAvailableListener, mBackgroundHandler);
748 |
749 | mCharacteristics = characteristics;
750 | mCameraId = cameraId;
751 | }
752 | return true;
753 | }
754 | } catch (CameraAccessException e) {
755 | e.printStackTrace();
756 | }
757 |
758 | // If we found no suitable cameras for capturing RAW, warn the user.
759 | ErrorDialog.buildErrorDialog("This device doesn't support capturing RAW photos").
760 | show(getFragmentManager(), "dialog");
761 | return false;
762 | }
763 |
764 | /**
765 | * Opens the camera specified by {@link #mCameraId}.
766 | */
767 | @SuppressWarnings("MissingPermission")
768 | private void openCamera() {
769 | if (!setUpCameraOutputs()) {
770 | return;
771 | }
772 | if (!hasAllPermissionsGranted()) {
773 | requestCameraPermissions();
774 | return;
775 | }
776 |
777 | Activity activity = getActivity();
778 | CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
779 | try {
780 | // Wait for any previously running session to finish.
781 | if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
782 | throw new RuntimeException("Time out waiting to lock camera opening.");
783 | }
784 |
785 | String cameraId;
786 | Handler backgroundHandler;
787 | synchronized (mCameraStateLock) {
788 | cameraId = mCameraId;
789 | backgroundHandler = mBackgroundHandler;
790 | }
791 |
792 | // Attempt to open the camera. mStateCallback will be called on the background handler's
793 | // thread when this succeeds or fails.
794 | manager.openCamera(cameraId, mStateCallback, backgroundHandler);
795 | } catch (CameraAccessException e) {
796 | e.printStackTrace();
797 | } catch (InterruptedException e) {
798 | throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
799 | }
800 | }
801 |
802 | /**
803 | * Requests permissions necessary to use camera and save pictures.
804 | */
805 | private void requestCameraPermissions() {
806 | if (shouldShowRationale()) {
807 | PermissionConfirmationDialog.newInstance().show(getChildFragmentManager(), "dialog");
808 | } else {
809 | FragmentCompat.requestPermissions(this, CAMERA_PERMISSIONS, REQUEST_CAMERA_PERMISSIONS);
810 | }
811 | }
812 |
813 | /**
814 | * Tells whether all the necessary permissions are granted to this app.
815 | *
816 | * @return True if all the required permissions are granted.
817 | */
818 | private boolean hasAllPermissionsGranted() {
819 | for (String permission : CAMERA_PERMISSIONS) {
820 | if (ActivityCompat.checkSelfPermission(getActivity(), permission)
821 | != PackageManager.PERMISSION_GRANTED) {
822 | return false;
823 | }
824 | }
825 | return true;
826 | }
827 |
828 | /**
829 | * Gets whether you should show UI with rationale for requesting the permissions.
830 | *
831 | * @return True if the UI should be shown.
832 | */
833 | private boolean shouldShowRationale() {
834 | for (String permission : CAMERA_PERMISSIONS) {
835 | if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) {
836 | return true;
837 | }
838 | }
839 | return false;
840 | }
841 |
842 | /**
843 | * Shows that this app really needs the permission and finishes the app.
844 | */
845 | private void showMissingPermissionError() {
846 | Activity activity = getActivity();
847 | if (activity != null) {
848 | Toast.makeText(activity, R.string.request_permission, Toast.LENGTH_SHORT).show();
849 | activity.finish();
850 | }
851 | }
852 |
853 | /**
854 | * Closes the current {@link CameraDevice}.
855 | */
856 | private void closeCamera() {
857 | try {
858 | mCameraOpenCloseLock.acquire();
859 | synchronized (mCameraStateLock) {
860 |
861 | // Reset state and clean up resources used by the camera.
862 | // Note: After calling this, the ImageReaders will be closed after any background
863 | // tasks saving Images from these readers have been completed.
864 | mPendingUserCaptures = 0;
865 | mState = STATE_CLOSED;
866 | if (null != mCaptureSession) {
867 | mCaptureSession.close();
868 | mCaptureSession = null;
869 | }
870 | if (null != mCameraDevice) {
871 | mCameraDevice.close();
872 | mCameraDevice = null;
873 | }
874 | if (null != mJpegImageReader) {
875 | mJpegImageReader.close();
876 | mJpegImageReader = null;
877 | }
878 | if (null != mRawImageReader) {
879 | mRawImageReader.close();
880 | mRawImageReader = null;
881 | }
882 | }
883 | } catch (InterruptedException e) {
884 | throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
885 | } finally {
886 | mCameraOpenCloseLock.release();
887 | }
888 | }
889 |
890 | /**
891 | * Starts a background thread and its {@link Handler}.
892 | */
893 | private void startBackgroundThread() {
894 | mBackgroundThread = new HandlerThread("CameraBackground");
895 | mBackgroundThread.start();
896 | synchronized (mCameraStateLock) {
897 | mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
898 | }
899 | }
900 |
901 | /**
902 | * Stops the background thread and its {@link Handler}.
903 | */
904 | private void stopBackgroundThread() {
905 | mBackgroundThread.quitSafely();
906 | try {
907 | mBackgroundThread.join();
908 | mBackgroundThread = null;
909 | synchronized (mCameraStateLock) {
910 | mBackgroundHandler = null;
911 | }
912 | } catch (InterruptedException e) {
913 | e.printStackTrace();
914 | }
915 | }
916 |
917 | /**
918 | * Creates a new {@link CameraCaptureSession} for camera preview.
919 | *
920 | * Call this only with {@link #mCameraStateLock} held.
921 | */
922 | private void createCameraPreviewSessionLocked() {
923 | try {
924 | SurfaceTexture texture = mTextureView.getSurfaceTexture();
925 | // We configure the size of default buffer to be the size of camera preview we want.
926 | texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
927 |
928 | // This is the output Surface we need to start preview.
929 | Surface surface = new Surface(texture);
930 |
931 | // We set up a CaptureRequest.Builder with the output Surface.
932 | mPreviewRequestBuilder
933 | = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
934 | mPreviewRequestBuilder.addTarget(surface);
935 |
936 | // Here, we create a CameraCaptureSession for camera preview.
937 | mCameraDevice.createCaptureSession(Arrays.asList(surface,
938 | mJpegImageReader.get().getSurface(),
939 | mRawImageReader.get().getSurface()), new CameraCaptureSession.StateCallback() {
940 | @Override
941 | public void onConfigured(CameraCaptureSession cameraCaptureSession) {
942 | synchronized (mCameraStateLock) {
943 | // The camera is already closed
944 | if (null == mCameraDevice) {
945 | return;
946 | }
947 |
948 | try {
949 | setup3AControlsLocked(mPreviewRequestBuilder);
950 | // Finally, we start displaying the camera preview.
951 | cameraCaptureSession.setRepeatingRequest(
952 | mPreviewRequestBuilder.build(),
953 | mPreCaptureCallback, mBackgroundHandler);
954 | mState = STATE_PREVIEW;
955 | } catch (CameraAccessException | IllegalStateException e) {
956 | e.printStackTrace();
957 | return;
958 | }
959 | // When the session is ready, we start displaying the preview.
960 | mCaptureSession = cameraCaptureSession;
961 | }
962 | }
963 |
964 | @Override
965 | public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
966 | showToast("Failed to configure camera.");
967 | }
968 | }, mBackgroundHandler
969 | );
970 | } catch (CameraAccessException e) {
971 | e.printStackTrace();
972 | }
973 | }
974 |
975 | /**
976 | * Configure the given {@link CaptureRequest.Builder} to use auto-focus, auto-exposure, and
977 | * auto-white-balance controls if available.
978 | *
979 | * Call this only with {@link #mCameraStateLock} held.
980 | *
981 | * @param builder the builder to configure.
982 | */
983 | private void setup3AControlsLocked(CaptureRequest.Builder builder) {
984 | // Enable auto-magical 3A run by camera device
985 | builder.set(CaptureRequest.CONTROL_MODE,
986 | CaptureRequest.CONTROL_MODE_AUTO);
987 |
988 | Float minFocusDist =
989 | mCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
990 |
991 | // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run.
992 | mNoAFRun = (minFocusDist == null || minFocusDist == 0);
993 |
994 | if (!mNoAFRun) {
995 | // If there is a "continuous picture" mode available, use it, otherwise default to AUTO.
996 | if (contains(mCharacteristics.get(
997 | CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES),
998 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
999 | builder.set(CaptureRequest.CONTROL_AF_MODE,
1000 | CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
1001 | } else {
1002 | builder.set(CaptureRequest.CONTROL_AF_MODE,
1003 | CaptureRequest.CONTROL_AF_MODE_AUTO);
1004 | }
1005 | }
1006 |
1007 | // If there is an auto-magical flash control mode available, use it, otherwise default to
1008 | // the "on" mode, which is guaranteed to always be available.
1009 | if (contains(mCharacteristics.get(
1010 | CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES),
1011 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) {
1012 | builder.set(CaptureRequest.CONTROL_AE_MODE,
1013 | CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
1014 | } else {
1015 | builder.set(CaptureRequest.CONTROL_AE_MODE,
1016 | CaptureRequest.CONTROL_AE_MODE_ON);
1017 | }
1018 |
1019 | // If there is an auto-magical white balance control mode available, use it.
1020 | if (contains(mCharacteristics.get(
1021 | CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES),
1022 | CaptureRequest.CONTROL_AWB_MODE_AUTO)) {
1023 | // Allow AWB to run auto-magically if this device supports this
1024 | builder.set(CaptureRequest.CONTROL_AWB_MODE,
1025 | CaptureRequest.CONTROL_AWB_MODE_AUTO);
1026 | }
1027 | }
1028 |
1029 | /**
1030 | * Configure the necessary {@link android.graphics.Matrix} transformation to `mTextureView`,
1031 | * and start/restart the preview capture session if necessary.
1032 | *
1033 | * This method should be called after the camera state has been initialized in
1034 | * setUpCameraOutputs.
1035 | *
1036 | * @param viewWidth The width of `mTextureView`
1037 | * @param viewHeight The height of `mTextureView`
1038 | */
1039 | private void configureTransform(int viewWidth, int viewHeight) {
1040 | Activity activity = getActivity();
1041 | synchronized (mCameraStateLock) {
1042 | if (null == mTextureView || null == activity) {
1043 | return;
1044 | }
1045 |
1046 | StreamConfigurationMap map = mCharacteristics.get(
1047 | CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1048 |
1049 | // For still image captures, we always use the largest available size.
1050 | Size largestJpeg = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
1051 | new CompareSizesByArea());
1052 |
1053 | // Find the rotation of the device relative to the native device orientation.
1054 | int deviceRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
1055 | Point displaySize = new Point();
1056 | activity.getWindowManager().getDefaultDisplay().getSize(displaySize);
1057 |
1058 | // Find the rotation of the device relative to the camera sensor's orientation.
1059 | int totalRotation = sensorToDeviceRotation(mCharacteristics, deviceRotation);
1060 |
1061 | // Swap the view dimensions for calculation as needed if they are rotated relative to
1062 | // the sensor.
1063 | boolean swappedDimensions = totalRotation == 90 || totalRotation == 270;
1064 | int rotatedViewWidth = viewWidth;
1065 | int rotatedViewHeight = viewHeight;
1066 | int maxPreviewWidth = displaySize.x;
1067 | int maxPreviewHeight = displaySize.y;
1068 |
1069 | if (swappedDimensions) {
1070 | rotatedViewWidth = viewHeight;
1071 | rotatedViewHeight = viewWidth;
1072 | maxPreviewWidth = displaySize.y;
1073 | maxPreviewHeight = displaySize.x;
1074 | }
1075 |
1076 | // Preview should not be larger than display size and 1080p.
1077 | if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
1078 | maxPreviewWidth = MAX_PREVIEW_WIDTH;
1079 | }
1080 |
1081 | if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
1082 | maxPreviewHeight = MAX_PREVIEW_HEIGHT;
1083 | }
1084 |
1085 | // Find the best preview size for these view dimensions and configured JPEG size.
1086 | Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
1087 | rotatedViewWidth, rotatedViewHeight, maxPreviewWidth, maxPreviewHeight,
1088 | largestJpeg);
1089 |
1090 | if (swappedDimensions) {
1091 | mTextureView.setAspectRatio(
1092 | previewSize.getHeight(), previewSize.getWidth());
1093 | } else {
1094 | mTextureView.setAspectRatio(
1095 | previewSize.getWidth(), previewSize.getHeight());
1096 | }
1097 |
1098 | // Find rotation of device in degrees (reverse device orientation for front-facing
1099 | // cameras).
1100 | int rotation = (mCharacteristics.get(CameraCharacteristics.LENS_FACING) ==
1101 | CameraCharacteristics.LENS_FACING_FRONT) ?
1102 | (360 + ORIENTATIONS.get(deviceRotation)) % 360 :
1103 | (360 - ORIENTATIONS.get(deviceRotation)) % 360;
1104 |
1105 | Matrix matrix = new Matrix();
1106 | RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
1107 | RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
1108 | float centerX = viewRect.centerX();
1109 | float centerY = viewRect.centerY();
1110 |
1111 | // Initially, output stream images from the Camera2 API will be rotated to the native
1112 | // device orientation from the sensor's orientation, and the TextureView will default to
1113 | // scaling these buffers to fill it's view bounds. If the aspect ratios and relative
1114 | // orientations are correct, this is fine.
1115 | //
1116 | // However, if the device orientation has been rotated relative to its native
1117 | // orientation so that the TextureView's dimensions are swapped relative to the
1118 | // native device orientation, we must do the following to ensure the output stream
1119 | // images are not incorrectly scaled by the TextureView:
1120 | // - Undo the scale-to-fill from the output buffer's dimensions (i.e. its dimensions
1121 | // in the native device orientation) to the TextureView's dimension.
1122 | // - Apply a scale-to-fill from the output buffer's rotated dimensions
1123 | // (i.e. its dimensions in the current device orientation) to the TextureView's
1124 | // dimensions.
1125 | // - Apply the rotation from the native device orientation to the current device
1126 | // rotation.
1127 | if (Surface.ROTATION_90 == deviceRotation || Surface.ROTATION_270 == deviceRotation) {
1128 | bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
1129 | matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
1130 | float scale = Math.max(
1131 | (float) viewHeight / previewSize.getHeight(),
1132 | (float) viewWidth / previewSize.getWidth());
1133 | matrix.postScale(scale, scale, centerX, centerY);
1134 |
1135 | }
1136 | matrix.postRotate(rotation, centerX, centerY);
1137 |
1138 | mTextureView.setTransform(matrix);
1139 |
1140 | // Start or restart the active capture session if the preview was initialized or
1141 | // if its aspect ratio changed significantly.
1142 | if (mPreviewSize == null || !checkAspectsEqual(previewSize, mPreviewSize)) {
1143 | mPreviewSize = previewSize;
1144 | if (mState != STATE_CLOSED) {
1145 | createCameraPreviewSessionLocked();
1146 | }
1147 | }
1148 | }
1149 | }
1150 |
1151 | /**
1152 | * Initiate a still image capture.
1153 | *
1154 | * This function sends a capture request that initiates a pre-capture sequence in our state
1155 | * machine that waits for auto-focus to finish, ending in a "locked" state where the lens is no
1156 | * longer moving, waits for auto-exposure to choose a good exposure value, and waits for
1157 | * auto-white-balance to converge.
1158 | */
1159 | private void takePicture() {
1160 | synchronized (mCameraStateLock) {
1161 | mPendingUserCaptures++;
1162 |
1163 | // If we already triggered a pre-capture sequence, or are in a state where we cannot
1164 | // do this, return immediately.
1165 | if (mState != STATE_PREVIEW) {
1166 | return;
1167 | }
1168 |
1169 | try {
1170 | // Trigger an auto-focus run if camera is capable. If the camera is already focused,
1171 | // this should do nothing.
1172 | if (!mNoAFRun) {
1173 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1174 | CameraMetadata.CONTROL_AF_TRIGGER_START);
1175 | }
1176 |
1177 | // If this is not a legacy device, we can also trigger an auto-exposure metering
1178 | // run.
1179 | if (!isLegacyLocked()) {
1180 | // Tell the camera to lock focus.
1181 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
1182 | CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_START);
1183 | }
1184 |
1185 | // Update state machine to wait for auto-focus, auto-exposure, and
1186 | // auto-white-balance (aka. "3A") to converge.
1187 | mState = STATE_WAITING_FOR_3A_CONVERGENCE;
1188 |
1189 | // Start a timer for the pre-capture sequence.
1190 | startTimerLocked();
1191 |
1192 | // Replace the existing repeating request with one with updated 3A triggers.
1193 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1194 | mBackgroundHandler);
1195 | } catch (CameraAccessException e) {
1196 | e.printStackTrace();
1197 | }
1198 | }
1199 | }
1200 |
1201 | /**
1202 | * Send a capture request to the camera device that initiates a capture targeting the JPEG and
1203 | * RAW outputs.
1204 | *
1205 | * Call this only with {@link #mCameraStateLock} held.
1206 | */
1207 | private void captureStillPictureLocked() {
1208 | try {
1209 | final Activity activity = getActivity();
1210 | if (null == activity || null == mCameraDevice) {
1211 | return;
1212 | }
1213 | // This is the CaptureRequest.Builder that we use to take a picture.
1214 | final CaptureRequest.Builder captureBuilder =
1215 | mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
1216 |
1217 | captureBuilder.addTarget(mJpegImageReader.get().getSurface());
1218 | captureBuilder.addTarget(mRawImageReader.get().getSurface());
1219 |
1220 | // Use the same AE and AF modes as the preview.
1221 | setup3AControlsLocked(captureBuilder);
1222 |
1223 | // Set orientation.
1224 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
1225 | captureBuilder.set(CaptureRequest.JPEG_ORIENTATION,
1226 | sensorToDeviceRotation(mCharacteristics, rotation));
1227 |
1228 | // Set request tag to easily track results in callbacks.
1229 | captureBuilder.setTag(mRequestCounter.getAndIncrement());
1230 |
1231 | CaptureRequest request = captureBuilder.build();
1232 |
1233 | // Create an ImageSaverBuilder in which to collect results, and add it to the queue
1234 | // of active requests.
1235 | ImageSaver.ImageSaverBuilder jpegBuilder = new ImageSaver.ImageSaverBuilder(activity)
1236 | .setCharacteristics(mCharacteristics);
1237 | ImageSaver.ImageSaverBuilder rawBuilder = new ImageSaver.ImageSaverBuilder(activity)
1238 | .setCharacteristics(mCharacteristics);
1239 |
1240 | mJpegResultQueue.put((int) request.getTag(), jpegBuilder);
1241 | mRawResultQueue.put((int) request.getTag(), rawBuilder);
1242 |
1243 | mCaptureSession.capture(request, mCaptureCallback, mBackgroundHandler);
1244 |
1245 | } catch (CameraAccessException e) {
1246 | e.printStackTrace();
1247 | }
1248 | }
1249 |
1250 | /**
1251 | * Called after a RAW/JPEG capture has completed; resets the AF trigger state for the
1252 | * pre-capture sequence.
1253 | *
1254 | * Call this only with {@link #mCameraStateLock} held.
1255 | */
1256 | private void finishedCaptureLocked() {
1257 | try {
1258 | // Reset the auto-focus trigger in case AF didn't run quickly enough.
1259 | if (!mNoAFRun) {
1260 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1261 | CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
1262 |
1263 | mCaptureSession.capture(mPreviewRequestBuilder.build(), mPreCaptureCallback,
1264 | mBackgroundHandler);
1265 |
1266 | mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
1267 | CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
1268 | }
1269 | } catch (CameraAccessException e) {
1270 | e.printStackTrace();
1271 | }
1272 | }
1273 |
1274 | /**
1275 | * Retrieve the next {@link Image} from a reference counted {@link ImageReader}, retaining
1276 | * that {@link ImageReader} until that {@link Image} is no longer in use, and set this
1277 | * {@link Image} as the result for the next request in the queue of pending requests. If
1278 | * all necessary information is available, begin saving the image to a file in a background
1279 | * thread.
1280 | *
1281 | * @param pendingQueue the currently active requests.
1282 | * @param reader a reference counted wrapper containing an {@link ImageReader} from which
1283 | * to acquire an image.
1284 | */
1285 | private void dequeueAndSaveImage(TreeMap pendingQueue,
1286 | RefCountedAutoCloseable reader) {
1287 | synchronized (mCameraStateLock) {
1288 | Map.Entry entry =
1289 | pendingQueue.firstEntry();
1290 | ImageSaver.ImageSaverBuilder builder = entry.getValue();
1291 |
1292 | // Increment reference count to prevent ImageReader from being closed while we
1293 | // are saving its Images in a background thread (otherwise their resources may
1294 | // be freed while we are writing to a file).
1295 | if (reader == null || reader.getAndRetain() == null) {
1296 | Log.e(TAG, "Paused the activity before we could save the image," +
1297 | " ImageReader already closed.");
1298 | pendingQueue.remove(entry.getKey());
1299 | return;
1300 | }
1301 |
1302 | Image image;
1303 | try {
1304 | image = reader.get().acquireNextImage();
1305 | } catch (IllegalStateException e) {
1306 | Log.e(TAG, "Too many images queued for saving, dropping image for request: " +
1307 | entry.getKey());
1308 | pendingQueue.remove(entry.getKey());
1309 | return;
1310 | }
1311 |
1312 | builder.setRefCountedReader(reader).setImage(image);
1313 |
1314 | handleCompletionLocked(entry.getKey(), builder, pendingQueue);
1315 | }
1316 | }
1317 |
1318 | /**
1319 | * Runnable that saves an {@link Image} into the specified {@link File}, and updates
1320 | * {@link android.provider.MediaStore} to include the resulting file.
1321 | *
1322 | * This can be constructed through an {@link ImageSaverBuilder} as the necessary image and
1323 | * result information becomes available.
1324 | */
1325 | private static class ImageSaver implements Runnable {
1326 |
1327 | /**
1328 | * The image to save.
1329 | */
1330 | private final Image mImage;
1331 | /**
1332 | * The file we save the image into.
1333 | */
1334 | private final File mFile;
1335 |
1336 | /**
1337 | * The CaptureResult for this image capture.
1338 | */
1339 | private final CaptureResult mCaptureResult;
1340 |
1341 | /**
1342 | * The CameraCharacteristics for this camera device.
1343 | */
1344 | private final CameraCharacteristics mCharacteristics;
1345 |
1346 | /**
1347 | * The Context to use when updating MediaStore with the saved images.
1348 | */
1349 | private final Context mContext;
1350 |
1351 | /**
1352 | * A reference counted wrapper for the ImageReader that owns the given image.
1353 | */
1354 | private final RefCountedAutoCloseable mReader;
1355 |
1356 | private ImageSaver(Image image, File file, CaptureResult result,
1357 | CameraCharacteristics characteristics, Context context,
1358 | RefCountedAutoCloseable reader) {
1359 | mImage = image;
1360 | mFile = file;
1361 | mCaptureResult = result;
1362 | mCharacteristics = characteristics;
1363 | mContext = context;
1364 | mReader = reader;
1365 | }
1366 |
1367 | @Override
1368 | public void run() {
1369 | boolean success = false;
1370 | int format = mImage.getFormat();
1371 | switch (format) {
1372 | case ImageFormat.JPEG: {
1373 | ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
1374 | byte[] bytes = new byte[buffer.remaining()];
1375 | buffer.get(bytes);
1376 | FileOutputStream output = null;
1377 | try {
1378 | output = new FileOutputStream(mFile);
1379 | output.write(bytes);
1380 | success = true;
1381 | } catch (IOException e) {
1382 | e.printStackTrace();
1383 | } finally {
1384 | mImage.close();
1385 | closeOutput(output);
1386 | }
1387 | break;
1388 | }
1389 | case ImageFormat.RAW_SENSOR: {
1390 | DngCreator dngCreator = new DngCreator(mCharacteristics, mCaptureResult);
1391 | FileOutputStream output = null;
1392 | try {
1393 | output = new FileOutputStream(mFile);
1394 | dngCreator.writeImage(output, mImage);
1395 | success = true;
1396 | } catch (IOException e) {
1397 | e.printStackTrace();
1398 | } finally {
1399 | mImage.close();
1400 | closeOutput(output);
1401 | }
1402 | break;
1403 | }
1404 | default: {
1405 | Log.e(TAG, "Cannot save image, unexpected image format:" + format);
1406 | break;
1407 | }
1408 | }
1409 |
1410 | // Decrement reference count to allow ImageReader to be closed to free up resources.
1411 | mReader.close();
1412 |
1413 | // If saving the file succeeded, update MediaStore.
1414 | if (success) {
1415 | MediaScannerConnection.scanFile(mContext, new String[]{mFile.getPath()},
1416 | /*mimeTypes*/null, new MediaScannerConnection.MediaScannerConnectionClient() {
1417 | @Override
1418 | public void onMediaScannerConnected() {
1419 | // Do nothing
1420 | }
1421 |
1422 | @Override
1423 | public void onScanCompleted(String path, Uri uri) {
1424 | Log.i(TAG, "Scanned " + path + ":");
1425 | Log.i(TAG, "-> uri=" + uri);
1426 | }
1427 | });
1428 | }
1429 | }
1430 |
1431 | /**
1432 | * Builder class for constructing {@link ImageSaver}s.
1433 | *
1434 | * This class is thread safe.
1435 | */
1436 | public static class ImageSaverBuilder {
1437 | private Image mImage;
1438 | private File mFile;
1439 | private CaptureResult mCaptureResult;
1440 | private CameraCharacteristics mCharacteristics;
1441 | private Context mContext;
1442 | private RefCountedAutoCloseable mReader;
1443 |
1444 | /**
1445 | * Construct a new ImageSaverBuilder using the given {@link Context}.
1446 | *
1447 | * @param context a {@link Context} to for accessing the
1448 | * {@link android.provider.MediaStore}.
1449 | */
1450 | public ImageSaverBuilder(final Context context) {
1451 | mContext = context;
1452 | }
1453 |
1454 | public synchronized ImageSaverBuilder setRefCountedReader(
1455 | RefCountedAutoCloseable reader) {
1456 | if (reader == null) throw new NullPointerException();
1457 |
1458 | mReader = reader;
1459 | return this;
1460 | }
1461 |
1462 | public synchronized ImageSaverBuilder setImage(final Image image) {
1463 | if (image == null) throw new NullPointerException();
1464 | mImage = image;
1465 | return this;
1466 | }
1467 |
1468 | public synchronized ImageSaverBuilder setFile(final File file) {
1469 | if (file == null) throw new NullPointerException();
1470 | mFile = file;
1471 | return this;
1472 | }
1473 |
1474 | public synchronized ImageSaverBuilder setResult(final CaptureResult result) {
1475 | if (result == null) throw new NullPointerException();
1476 | mCaptureResult = result;
1477 | return this;
1478 | }
1479 |
1480 | public synchronized ImageSaverBuilder setCharacteristics(
1481 | final CameraCharacteristics characteristics) {
1482 | if (characteristics == null) throw new NullPointerException();
1483 | mCharacteristics = characteristics;
1484 | return this;
1485 | }
1486 |
1487 | public synchronized ImageSaver buildIfComplete() {
1488 | if (!isComplete()) {
1489 | return null;
1490 | }
1491 | return new ImageSaver(mImage, mFile, mCaptureResult, mCharacteristics, mContext,
1492 | mReader);
1493 | }
1494 |
1495 | public synchronized String getSaveLocation() {
1496 | return (mFile == null) ? "Unknown" : mFile.toString();
1497 | }
1498 |
1499 | private boolean isComplete() {
1500 | return mImage != null && mFile != null && mCaptureResult != null
1501 | && mCharacteristics != null;
1502 | }
1503 | }
1504 | }
1505 |
1506 | // Utility classes and methods:
1507 | // *********************************************************************************************
1508 |
1509 | /**
1510 | * Comparator based on area of the given {@link Size} objects.
1511 | */
1512 | static class CompareSizesByArea implements Comparator {
1513 |
1514 | @Override
1515 | public int compare(Size lhs, Size rhs) {
1516 | // We cast here to ensure the multiplications won't overflow
1517 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
1518 | (long) rhs.getWidth() * rhs.getHeight());
1519 | }
1520 |
1521 | }
1522 |
1523 | /**
1524 | * A dialog fragment for displaying non-recoverable errors; this {@ling Activity} will be
1525 | * finished once the dialog has been acknowledged by the user.
1526 | */
1527 | public static class ErrorDialog extends DialogFragment {
1528 |
1529 | private String mErrorMessage;
1530 |
1531 | public ErrorDialog() {
1532 | mErrorMessage = "Unknown error occurred!";
1533 | }
1534 |
1535 | // Build a dialog with a custom message (Fragments require default constructor).
1536 | public static ErrorDialog buildErrorDialog(String errorMessage) {
1537 | ErrorDialog dialog = new ErrorDialog();
1538 | dialog.mErrorMessage = errorMessage;
1539 | return dialog;
1540 | }
1541 |
1542 | @Override
1543 | public Dialog onCreateDialog(Bundle savedInstanceState) {
1544 | final Activity activity = getActivity();
1545 | return new AlertDialog.Builder(activity)
1546 | .setMessage(mErrorMessage)
1547 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1548 | @Override
1549 | public void onClick(DialogInterface dialogInterface, int i) {
1550 | activity.finish();
1551 | }
1552 | })
1553 | .create();
1554 | }
1555 | }
1556 |
1557 | /**
1558 | * A wrapper for an {@link AutoCloseable} object that implements reference counting to allow
1559 | * for resource management.
1560 | */
1561 | public static class RefCountedAutoCloseable implements AutoCloseable {
1562 | private T mObject;
1563 | private long mRefCount = 0;
1564 |
1565 | /**
1566 | * Wrap the given object.
1567 | *
1568 | * @param object an object to wrap.
1569 | */
1570 | public RefCountedAutoCloseable(T object) {
1571 | if (object == null) throw new NullPointerException();
1572 | mObject = object;
1573 | }
1574 |
1575 | /**
1576 | * Increment the reference count and return the wrapped object.
1577 | *
1578 | * @return the wrapped object, or null if the object has been released.
1579 | */
1580 | public synchronized T getAndRetain() {
1581 | if (mRefCount < 0) {
1582 | return null;
1583 | }
1584 | mRefCount++;
1585 | return mObject;
1586 | }
1587 |
1588 | /**
1589 | * Return the wrapped object.
1590 | *
1591 | * @return the wrapped object, or null if the object has been released.
1592 | */
1593 | public synchronized T get() {
1594 | return mObject;
1595 | }
1596 |
1597 | /**
1598 | * Decrement the reference count and release the wrapped object if there are no other
1599 | * users retaining this object.
1600 | */
1601 | @Override
1602 | public synchronized void close() {
1603 | if (mRefCount >= 0) {
1604 | mRefCount--;
1605 | if (mRefCount < 0) {
1606 | try {
1607 | mObject.close();
1608 | } catch (Exception e) {
1609 | throw new RuntimeException(e);
1610 | } finally {
1611 | mObject = null;
1612 | }
1613 | }
1614 | }
1615 | }
1616 | }
1617 |
1618 | /**
1619 | * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
1620 | * is at least as large as the respective texture view size, and that is at most as large as the
1621 | * respective max size, and whose aspect ratio matches with the specified value. If such size
1622 | * doesn't exist, choose the largest one that is at most as large as the respective max size,
1623 | * and whose aspect ratio matches with the specified value.
1624 | *
1625 | * @param choices The list of sizes that the camera supports for the intended output
1626 | * class
1627 | * @param textureViewWidth The width of the texture view relative to sensor coordinate
1628 | * @param textureViewHeight The height of the texture view relative to sensor coordinate
1629 | * @param maxWidth The maximum width that can be chosen
1630 | * @param maxHeight The maximum height that can be chosen
1631 | * @param aspectRatio The aspect ratio
1632 | * @return The optimal {@code Size}, or an arbitrary one if none were big enough
1633 | */
1634 | private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
1635 | int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
1636 | // Collect the supported resolutions that are at least as big as the preview Surface
1637 | List bigEnough = new ArrayList<>();
1638 | // Collect the supported resolutions that are smaller than the preview Surface
1639 | List notBigEnough = new ArrayList<>();
1640 | int w = aspectRatio.getWidth();
1641 | int h = aspectRatio.getHeight();
1642 | for (Size option : choices) {
1643 | if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
1644 | option.getHeight() == option.getWidth() * h / w) {
1645 | if (option.getWidth() >= textureViewWidth &&
1646 | option.getHeight() >= textureViewHeight) {
1647 | bigEnough.add(option);
1648 | } else {
1649 | notBigEnough.add(option);
1650 | }
1651 | }
1652 | }
1653 |
1654 | // Pick the smallest of those big enough. If there is no one big enough, pick the
1655 | // largest of those not big enough.
1656 | if (bigEnough.size() > 0) {
1657 | return Collections.min(bigEnough, new CompareSizesByArea());
1658 | } else if (notBigEnough.size() > 0) {
1659 | return Collections.max(notBigEnough, new CompareSizesByArea());
1660 | } else {
1661 | Log.e(TAG, "Couldn't find any suitable preview size");
1662 | return choices[0];
1663 | }
1664 | }
1665 |
1666 | /**
1667 | * Generate a string containing a formatted timestamp with the current date and time.
1668 | *
1669 | * @return a {@link String} representing a time.
1670 | */
1671 | private static String generateTimestamp() {
1672 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US);
1673 | return sdf.format(new Date());
1674 | }
1675 |
1676 | /**
1677 | * Cleanup the given {@link OutputStream}.
1678 | *
1679 | * @param outputStream the stream to close.
1680 | */
1681 | private static void closeOutput(OutputStream outputStream) {
1682 | if (null != outputStream) {
1683 | try {
1684 | outputStream.close();
1685 | } catch (IOException e) {
1686 | e.printStackTrace();
1687 | }
1688 | }
1689 | }
1690 |
1691 | /**
1692 | * Return true if the given array contains the given integer.
1693 | *
1694 | * @param modes array to check.
1695 | * @param mode integer to get for.
1696 | * @return true if the array contains the given integer, otherwise false.
1697 | */
1698 | private static boolean contains(int[] modes, int mode) {
1699 | if (modes == null) {
1700 | return false;
1701 | }
1702 | for (int i : modes) {
1703 | if (i == mode) {
1704 | return true;
1705 | }
1706 | }
1707 | return false;
1708 | }
1709 |
1710 | /**
1711 | * Return true if the two given {@link Size}s have the same aspect ratio.
1712 | *
1713 | * @param a first {@link Size} to compare.
1714 | * @param b second {@link Size} to compare.
1715 | * @return true if the sizes have the same aspect ratio, otherwise false.
1716 | */
1717 | private static boolean checkAspectsEqual(Size a, Size b) {
1718 | double aAspect = a.getWidth() / (double) a.getHeight();
1719 | double bAspect = b.getWidth() / (double) b.getHeight();
1720 | return Math.abs(aAspect - bAspect) <= ASPECT_RATIO_TOLERANCE;
1721 | }
1722 |
1723 | /**
1724 | * Rotation need to transform from the camera sensor orientation to the device's current
1725 | * orientation.
1726 | *
1727 | * @param c the {@link CameraCharacteristics} to query for the camera sensor
1728 | * orientation.
1729 | * @param deviceOrientation the current device orientation relative to the native device
1730 | * orientation.
1731 | * @return the total rotation from the sensor orientation to the current device orientation.
1732 | */
1733 | private static int sensorToDeviceRotation(CameraCharacteristics c, int deviceOrientation) {
1734 | int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);
1735 |
1736 | // Get device orientation in degrees
1737 | deviceOrientation = ORIENTATIONS.get(deviceOrientation);
1738 |
1739 | // Reverse device orientation for front-facing cameras
1740 | if (c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
1741 | deviceOrientation = -deviceOrientation;
1742 | }
1743 |
1744 | // Calculate desired JPEG orientation relative to camera orientation to make
1745 | // the image upright relative to the device orientation
1746 | return (sensorOrientation - deviceOrientation + 360) % 360;
1747 | }
1748 |
1749 | /**
1750 | * Shows a {@link Toast} on the UI thread.
1751 | *
1752 | * @param text The message to show.
1753 | */
1754 | private void showToast(String text) {
1755 | // We show a Toast by sending request message to mMessageHandler. This makes sure that the
1756 | // Toast is shown on the UI thread.
1757 | Message message = Message.obtain();
1758 | message.obj = text;
1759 | mMessageHandler.sendMessage(message);
1760 | }
1761 |
1762 | /**
1763 | * If the given request has been completed, remove it from the queue of active requests and
1764 | * send an {@link ImageSaver} with the results from this request to a background thread to
1765 | * save a file.
1766 | *
1767 | * Call this only with {@link #mCameraStateLock} held.
1768 | *
1769 | * @param requestId the ID of the {@link CaptureRequest} to handle.
1770 | * @param builder the {@link ImageSaver.ImageSaverBuilder} for this request.
1771 | * @param queue the queue to remove this request from, if completed.
1772 | */
1773 | private void handleCompletionLocked(int requestId, ImageSaver.ImageSaverBuilder builder,
1774 | TreeMap queue) {
1775 | if (builder == null) return;
1776 | ImageSaver saver = builder.buildIfComplete();
1777 | if (saver != null) {
1778 | queue.remove(requestId);
1779 | AsyncTask.THREAD_POOL_EXECUTOR.execute(saver);
1780 | }
1781 | }
1782 |
1783 | /**
1784 | * Check if we are using a device that only supports the LEGACY hardware level.
1785 | *
1786 | * Call this only with {@link #mCameraStateLock} held.
1787 | *
1788 | * @return true if this is a legacy device.
1789 | */
1790 | private boolean isLegacyLocked() {
1791 | return mCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
1792 | CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
1793 | }
1794 |
1795 | /**
1796 | * Start the timer for the pre-capture sequence.
1797 | *
1798 | * Call this only with {@link #mCameraStateLock} held.
1799 | */
1800 | private void startTimerLocked() {
1801 | mCaptureTimer = SystemClock.elapsedRealtime();
1802 | }
1803 |
1804 | /**
1805 | * Check if the timer for the pre-capture sequence has been hit.
1806 | *
1807 | * Call this only with {@link #mCameraStateLock} held.
1808 | *
1809 | * @return true if the timeout occurred.
1810 | */
1811 | private boolean hitTimeoutLocked() {
1812 | return (SystemClock.elapsedRealtime() - mCaptureTimer) > PRECAPTURE_TIMEOUT_MS;
1813 | }
1814 |
1815 | /**
1816 | * A dialog that explains about the necessary permissions.
1817 | */
1818 | public static class PermissionConfirmationDialog extends DialogFragment {
1819 |
1820 | public static PermissionConfirmationDialog newInstance() {
1821 | return new PermissionConfirmationDialog();
1822 | }
1823 |
1824 | @Override
1825 | public Dialog onCreateDialog(Bundle savedInstanceState) {
1826 | final Fragment parent = getParentFragment();
1827 | return new AlertDialog.Builder(getActivity())
1828 | .setMessage(R.string.request_permission)
1829 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
1830 | @Override
1831 | public void onClick(DialogInterface dialog, int which) {
1832 | FragmentCompat.requestPermissions(parent, CAMERA_PERMISSIONS,
1833 | REQUEST_CAMERA_PERMISSIONS);
1834 | }
1835 | })
1836 | .setNegativeButton(android.R.string.cancel,
1837 | new DialogInterface.OnClickListener() {
1838 | @Override
1839 | public void onClick(DialogInterface dialog, int which) {
1840 | getActivity().finish();
1841 | }
1842 | })
1843 | .create();
1844 | }
1845 |
1846 | }
1847 |
1848 | }
1849 |
--------------------------------------------------------------------------------