Search code examples
javaandroidandroid-studioandroid-cameraazure-cognitive-services

Why is the " TAKE A PHOTO" button not working?


I tried doing some application so I borrowed one from GitHub. When I click the "TAKE A PHOTO" button, it says that the application is not responding. I think the problem has something to do with requesting permission with camera. I upgraded targetSdkVersion from 22 to 28 and now it is not working. Can anyone help me.

Here's the selectimageactivity.java:

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import com.microsoft.projectoxford.face.samples.R;

import java.io.File;
import java.io.IOException;

// The activity for the user to select a image and to detect faces in the image.
public class SelectImageActivity extends AppCompatActivity {
    // Flag to indicate the request of the next task to be performed
    private static final int REQUEST_TAKE_PHOTO = 0;
    private static final int REQUEST_SELECT_IMAGE_IN_ALBUM = 1;

    // The URI of photo taken with camera
    private Uri mUriPhotoTaken;

    // When the activity is created, set all the member variables to initial state.
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_select_image);
    }

    // Save the activity state when it's going to stop.
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putParcelable("ImageUri", mUriPhotoTaken);
    }

    // Recover the saved state when the activity is recreated.
    @Override
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        mUriPhotoTaken = savedInstanceState.getParcelable("ImageUri");
    }

    // Deal with the result of selection of the photos and faces.
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode)
        {
            case REQUEST_TAKE_PHOTO:
            case REQUEST_SELECT_IMAGE_IN_ALBUM:
                if (resultCode == RESULT_OK) {
                    Uri imageUri;
                    if (data == null || data.getData() == null) {
                        imageUri = mUriPhotoTaken;
                    } else {
                        imageUri = data.getData();
                    }
                    Intent intent = new Intent();
                    intent.setData(imageUri);
                    setResult(RESULT_OK, intent);
                    finish();
                }
                break;
            default:
                break;
        }
    }

    // When the button of "Take a Photo with Camera" is pressed.
    public void takePhoto(View view) {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if(intent.resolveActivity(getPackageManager()) != null) {
            // Save the photo taken to a temporary file.
            File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
            try {
                File file = File.createTempFile("IMG_", ".jpg", storageDir);
                mUriPhotoTaken = Uri.fromFile(file);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mUriPhotoTaken);
                startActivityForResult(intent, REQUEST_TAKE_PHOTO);
            } catch (IOException e) {
                setInfo(e.getMessage());
            }
        }
    }

    // When the button of "Select a Photo in Album" is pressed.
    public void selectImageInAlbum(View view) {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        if (intent.resolveActivity(getPackageManager()) != null) {
            startActivityForResult(intent, REQUEST_SELECT_IMAGE_IN_ALBUM);
        }
    }

    // Set the information panel on screen.
    private void setInfo(String info) {
        TextView textView = (TextView) findViewById(R.id.info);
        textView.setText(info);
    }
}

And then the build.gradle(Module):

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28

    defaultConfig {
        applicationId "com.microsoft.projectoxford.faceapisample"
        minSdkVersion 22
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')

    // Include local lib mostly for debug purpose.
    // implementation project(':lib')

    // Use the following line to include client library for Face API from Maven Central Repository
    implementation 'com.microsoft.projectoxford:face:1.4.4'
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.android.support.constraint:constraint-layout:2.0.4'


    implementation "com.android.support:design:28.0.0"

    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

}

This is the error I get at logcat. enter image description here


Solution

  • From your logcat, you are getting FileUriExposedExceptiom You need to create FileProvider and use the same.

    Reason being for apps targeting Android 7.0 (API level 24) and higher, passing a file://URI across a package boundary causes a FileUriExposedException.

    Here's for more info and how to take-photo and use FileProvider

    https://developer.android.com/training/camera/photobasics

    https://developer.android.com/reference/androidx/core/content/FileProvider

    First, you must declare this FileProvider in your AndroidManifest.xml file within the tag:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <application
            ...>
            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="com.example.myapp.fileprovider"
                android:grantUriPermissions="true"
                android:exported="false">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/filepaths" />
            </provider>
            ...
        </application>
    </manifest>
    

    Next, create a resource directory called xml and create a fileprovider.xml. Assuming you wish to grant access to the application's specific external storage directory, which requires requesting no additional permissions, you can declare this line as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <!-- See table below.  The external-files-path DOES NOT require external storage permissions. -->
        <external-files-path
            name="images"
            path="Pictures" />
    
        <!--Uncomment below to share the entire application specific directory -->
        <!--<external-path name="all_dirs" path="."/>-->
    </paths>
    

    Finally, you will convert the File object into a content provider using the FileProvider class:

    // getExternalFilesDir() + "/Pictures" should match the declaration in fileprovider.xml paths
    File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "share_image_" + System.currentTimeMillis() + ".png");
    
    // wrap File object into a content provider. NOTE: authority here should match authority in manifest declaration
    bmpUri = FileProvider.getUriForFile(MyActivity.this, "com.example.myapp.fileprovider", file);