The next step is to actually take the picture. This is the easy part: You get to use an implicit intent again.
Start by stashing the location of the photo file. You will use it a few more times, so this will save a bit of work.
Listing 16.10 Grabbing the photo file location (CrimeFragment.kt
)
class CrimeFragment : Fragment(), DatePickerFragment.Callbacks { private lateinit var crime: Crime private lateinit var photoFile: File ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... crimeDetailViewModel.crimeLiveData.observe( viewLifecycleOwner, Observer { crime -> crime?.let { this.crime = crime photoFile = crimeDetailViewModel.getPhotoFile(crime) updateUI() } }) } ... }
Next you will hook up the camera button to actually take the picture. The camera intent is defined in MediaStore, Android’s lord and master of all things media related. You will send an intent with an action of MediaStore.ACTION_IMAGE_CAPTURE, and Android will fire up a camera activity and take a picture for you.
But hold that thought for one minute.
Now you are ready to fire the camera intent.
The action you want is called ACTION_IMAGE_CAPTURE
, and it is defined in the MediaStore class.
MediaStore defines the public interfaces used in Android for interacting with common media – images, videos, and music.
This includes the image capture intent, which fires up the camera.
By default, ACTION_IMAGE_CAPTURE
will dutifully fire up the camera application and take a picture, but it will not be a full-resolution picture.
Instead, it will take a small-resolution thumbnail picture and stick it inside the Intent object returned in onActivityResult(…).
For a full-resolution output, you need to tell it where to save the image on the filesystem. This can be done by passing a Uri pointing to where you want to save the file in MediaStore.EXTRA_OUTPUT. This Uri will point to a location serviced by FileProvider.
First, create a new property for the photo URI and initialize it after you have a reference to the photoFile
.
Listing 16.11 Adding a photo URI property (CrimeFragment.kt
)
class CrimeFragment : Fragment(), DatePickerFragment.Callbacks { private lateinit var crime: Crime private lateinit var photoFile: File private lateinit var photoUri: Uri ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... crimeDetailViewModel.crimeLiveData.observe( viewLifecycleOwner, Observer { crime -> crime?.let { this.crime = crime photoFile = crimeDetailViewModel.getPhotoFile(crime) photoUri = FileProvider.getUriForFile(requireActivity(), "com.bignerdranch.android.criminalintent.fileprovider", photoFile) updateUI() } }) } ... }
Calling FileProvider.getUriForFile(…) translates your local file path into a Uri the camera app can see. The function takes in your activity, provider authority, and photo file to create the URI that points to the file. The authority string you pass to FileProvider.getUriForFile(…) must match the authority string you defined in the manifest (Listing 16.4).
Next, write an implicit intent to ask for a new picture to be taken into the location saved in photoUri (Listing 16.12). Add code to ensure that the button is disabled if there is no camera app or if there is no location to save the photo to. (To determine whether there is a camera app available, you will query PackageManager for activities that respond to your camera implicit intent, as discussed in the section called Checking for responding activities in Chapter 15.)
Listing 16.12 Firing a camera intent (CrimeFragment.kt
)
private const val REQUEST_CONTACT = 1 private const val REQUEST_PHOTO = 2 private const val DATE_FORMAT = "EEE, MMM, dd" class CrimeFragment : Fragment(), DatePickerFragment.Callbacks { ... override fun onStart() { ... suspectButton.apply { ... } photoButton.apply { val packageManager: PackageManager = requireActivity().packageManager val captureImage = Intent(MediaStore.ACTION_IMAGE_CAPTURE) val resolvedActivity: ResolveInfo? = packageManager.resolveActivity(captureImage, PackageManager.MATCH_DEFAULT_ONLY) if (resolvedActivity == null) { isEnabled = false } setOnClickListener { captureImage.putExtra(MediaStore.EXTRA_OUTPUT, photoUri) val cameraActivities: List<ResolveInfo> = packageManager.queryIntentActivities(captureImage, PackageManager.MATCH_DEFAULT_ONLY) for (cameraActivity in cameraActivities) { requireActivity().grantUriPermission( cameraActivity.activityInfo.packageName, photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } startActivityForResult(captureImage, REQUEST_PHOTO) } } return view } ... }
To actually write to photoUri
, you need to grant the camera app permission.
To do this, you grant the Intent.FLAG_GRANT_WRITE_URI_PERMISSION
flag to every activity your cameraImage intent can resolve to.
That grants them all a write permission specifically for this one Uri.
Adding the android:grantUriPermissions
attribute in your provider declaration was necessary to open this bit of functionality.
Later, you will revoke this permission to close up that gap in your armor again.
Run CriminalIntent and press the camera button to run your camera app (Figure 16.2).
3.12.161.77