Camera
Make use of the device camera to take and serialize a photo, display it as thumbnail and create a simple single-photo gallery.
Preview
In this lab you will use the device camera to take a photo.
- The photo may be saved to a file
- It will appear as a thumbnail in the residence details view
- An enlarged image will be viewable in a one-photo gallery
The following figures provide an outline.
Resources
Manifest
Proceeding with the application as completed to the end of the previous lab, we shall add two new activities, the content for which is provided in a later step:
- ResidenceCameraActivity
- Manages the taking of a photo
- ResidenceGalleryActivity
- Allows the display of the photo in a single-photo gallery.
Modify manifest file: add following snippets.
<activity
android:name=".activities.ResidenceCameraActivity"
android:label="@string/app_name">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ResidencePagerActivity"/>
</activity>
<activity
android:name=".activities.ResidenceGalleryActivity"
android:label="@string/app_name">
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.ResidencePagerActivity"/>
</activity>
Residence details view layout
We add a Camera button and a thumnail placeholder to the Residence details view:
- layout/fragment_residence.xml
First add 2 string resources:
<string name="take_photo">Take Photo</string>
<string name="save_photo">Save Photo</string>
Here is the additional code:
<!-- Camera button + thumbnail-->
<ImageButton
android:id="@+id/camera_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerInside"
android:layout_marginTop="16dp"
android:src="@android:drawable/ic_menu_camera"
android:layout_gravity="top"/>
<ImageView
android:id="@+id/myrent_imageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerInside"
android:background="@android:color/darker_gray"
android:cropToPadding="true"
android:layout_marginTop="16dp"
android:layout_gravity="top|end"
android:layout_margin="16dp"/>
As you can see, the code is located immediately following the geolocation input window.
For reference, here is the refactored fragment_residence.xml file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- LOCATION -->
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="51dp"
android:baselineAligned="false"
android:orientation="horizontal"
android:weightSum="1">
<!-- Geolocation (GPS Coords) -->
<EditText
android:id="@+id/geolocation"
android:layout_width="194dp"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint"
android:layout_weight="0.75">
<requestFocus />
</EditText>
<!-- Camera button + thumbnail-->
<ImageButton
android:id="@+id/camera_button"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerInside"
android:layout_marginTop="16dp"
android:src="@android:drawable/ic_menu_camera"
android:layout_gravity="top"/>
<ImageView
android:id="@+id/myrent_imageView"
android:layout_width="40dp"
android:layout_height="40dp"
android:scaleType="centerInside"
android:background="@android:color/darker_gray"
android:cropToPadding="true"
android:layout_marginTop="16dp"
android:layout_gravity="top|end"
android:layout_margin="16dp"/>
</LinearLayout>
<!-- STATUS -->
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status" />
<Button android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
<!-- Checkbox -->
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/isrented"
android:id="@+id/isrented"
android:checked="false"/>
<Button
android:id="@+id/tenant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/landlord" />
<Button android:id="@+id/residence_reportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/residence_report"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_blue_marker"/>
</LinearLayout>
Camera layout
Next we add a layout for the Camera activity:
File: /res/layout/residence_photo.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="org.wit.myrent.activities.ResidenceCameraActivity" >
<ImageView
android:id="@+id/residenceImage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/takePhoto"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:src="@mipmap/ic_lens"/>
<Button
android:id="@+id/takePhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/residenceImage"
android:layout_alignParentBottom="true"
android:text="@string/take_photo" />
<Button
android:id="@+id/savePhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/takePhoto"
android:layout_alignRight="@+id/residenceImage"
android:layout_alignTop="@+id/takePhoto"
android:text="@string/save_photo" />
</RelativeLayout>
Download the image ic_lens.png from here to a temp location.
- In Android Studio select res/mipmap, right click and select
new|Image Asset
. - The
Configure Image Asset
window should open. - Change the name to
ic_lens
. - Browse to where you have saved the ic_lens image and select it.
- The result should be as represented in Figure 3.
- Overwrite ic_lens if it already exist.
- Complete the remaining steps in the configuration wizard.
Emulator
If using an Android emulator ensure you enable at least one camera simulator in the settings. This is illustrated in Figure 5.
Model
The model Residence class requires a new field to represent the photo filename:
- Add and initialize with a placeholder name to avoid null pointer issues.
public String photo;
public Residence()
{
...
photo = "photo";
}
For reference here is the updated Residence:
package org.wit.myrent.models;
import android.content.Context;
import org.wit.myrent.R;
import java.util.Date;
import java.util.Random;
public class Residence
{
public Long id;
public Long date;
//a latitude longitude pair
//example "52.4566,-6.5444"
public String geolocation;
public boolean rented;
public String tenant;
public double zoom ;//zoom level of accompanying map
public String photo;
public Residence() {
id = unsignedLong();
date = new Date().getTime();
geolocation = "52.253456,-7.187162";
tenant = "none presently";
zoom = 16.0f;
photo = "photo";
}
public void setGeolocation(String geolocation) {
this.geolocation = geolocation;
}
public String getGeolocation() {
return geolocation;
}
public String getDateString() {
return "Registered:" + dateString();
}
private String dateString() {
String dateFormat = "EEE d MMM yyyy H:mm";
return android.text.format.DateFormat.format(dateFormat, date).toString();
}
public String getResidenceReport(Context context) {
String rentedString = "";
if (rented) {
rentedString = context.getString(R.string.residence_report_rented);
}
else {
rentedString = context.getString(R.string.residence_report_not_rented);
}
String prospectiveTenant = tenant;
if (tenant == null) {
prospectiveTenant = context.getString(R.string.residence_report_nobody_interested);
}
else {
prospectiveTenant = context.getString(R.string.residence_report_prospective_tenant, tenant);
}
String report = "Location " + geolocation + " Date: " + dateString() + " " + rentedString + " " + prospectiveTenant;
return report;
}
/**
* Generate a long greater than zero
* @return Unsigned Long value greater than zero
*/
private Long unsignedLong() {
long rndVal = 0;
do {
rndVal = new Random().nextLong();
} while (rndVal <= 0);
return rndVal;
}
}
Helpers
We will now add two helper classes, CameraHelper & FileIOHelper.
CameraHelper class has 2 methods:
- showPhoto
- displays the photo
- getScaledDrawable
- scales photo to fit in the application's imageview
These methods have been obtained from Android Programming by Hardy & Phillips.
- showPhoto
CameraHelper
File: org.wit.android.helpers.CameraHelper.java
package org.wit.android.helpers;
import java.util.List;
import org.wit.myrent.models.Residence;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.Camera.Size;
import android.view.Display;
import android.widget.ImageView;
public class CameraHelper
{
/**
* Render the photo on the ImageView
*/
public static void showPhoto(Activity activity, Residence res, ImageView photoView)
{
String path = activity.getFileStreamPath(res.photo).getAbsolutePath();
BitmapDrawable b = getScaledDrawable(activity, path);
if (b != null)
photoView.setImageDrawable(b);
}
/**
* Get a BitmapDrawable from a local file that is scaled down to fit the
* current Window size.
*/
@SuppressWarnings("deprecation")
public static BitmapDrawable getScaledDrawable(Activity a, String path)
{
Display display = a.getWindowManager().getDefaultDisplay();
float destWidth = display.getWidth();
float destHeight = display.getHeight();
// read in the dimensions of the image on disk
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > destHeight || srcWidth > destWidth)
{
if (srcWidth > srcHeight)
{
inSampleSize = Math.round((float) srcHeight / (float) destHeight);
}
else
{
inSampleSize = Math.round((float) srcWidth / (float) destWidth);
}
}
options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return new BitmapDrawable(a.getResources(), bitmap);
}
}
FileIOHelper
Here is the FileIOHelper class:
package org.wit.android.helpers;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import android.content.Context;
import android.graphics.Bitmap;
public class FileIOHelper
{
public static boolean write(Context context, String filename, byte[] data)
{
FileOutputStream os = null;
boolean success = true;
try
{
os = context.openFileOutput(filename, Context.MODE_PRIVATE);
os.write(data);
}
catch (Exception e)
{
LogHelpers.info(context,"Error writing to file " + filename + " " + e.getMessage());
success = false;
}
finally
{
try
{
if (os != null)
os.close();
}
catch (Exception e)
{
LogHelpers.info(context, "Error closing file " + filename + " " + e.getMessage());
success = false;
}
}
return success;
}
public static byte[] byteArray(Bitmap bmp)
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
public static boolean writeBitmap(Context context, String filename, Bitmap bmp)
{
return write(context, filename, byteArray(bmp));
}
}
Camera Activity
Note: Not all necessary import statements are supplied as accompaniment to the various code snipptes below. A complete set of statements is provided at the end of this file for reference purposes.
Create a new activity ResidenceCameraActivity in the package org.wit.myrent.activities.
package org.wit.myrent.activities;
import org.wit.myrent.R;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class ResidenceCameraActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.residence_photo);
}
}
Introduce and wire up the Up button so that it behaves similarly to the back button. We can do this by invoking Activity.onBackPressed
- Display and enable the Up button by adding this statement to the onCreate method following setContentView:
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- Override onOptionsItemSelected to act on pressing the Up button:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Add an import statement for MenuItem:
import android.view.MenuItem;
There are two buttons in the view: Take Photo and Save Photo.
- Add fields for these
private Button savePhoto;
private Button takePhoto;
Import the Button class:
import android.widget.Button;
Add an ImageView field in which the photo will be displayed.
private ImageView residenceImage;
Import the ImageView class:
import android.widget.ImageView;
Bind the widget instances to their respective layout elements. The following code which should be added to onCreate does this:
residenceImage = (ImageView) findViewById(R.id.residenceImage);
savePhoto = (Button)findViewById(R.id.savePhoto);
takePhoto = (Button)findViewById(R.id.takePhoto);
Set the default state of the save photo button to disabled. We will only enable this once a photo has been taken.
savePhoto.setEnabled(false);
Implement an OnClickListener interface:
public class ResidenceCameraActivity extends AppCompatActivity implements OnClickListener
Import the listener interface:
import android.view.View.OnClickListener;
Add the listener method skeleton:
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
}
Register listeners in onCreate to respond to button clicks:
savePhoto.setOnClickListener(this);
takePhoto.setOnClickListener(this);
In onClick method respond to clicking the buttons. For convenience we will locate most of the necessary code, as shown below, in two private methods:
- onTakePhotoClicked
- onPictureTaken
@Override
public void onClick(View v)
{
switch(v.getId())
{
case R.id.takePhoto : onTakePhotoClicked(v);
break;
case R.id.savePhoto : onPictureTaken(residencePhoto);
break;
}
}
The approach we adopt is to use an implicit Intent to make use of the device camera (which we check is available). Here is the implementation of onTakePhotoClicked:
public void onTakePhotoClicked(View v)
{
// Check for presence of device camera. If not present advise user and quit method.
PackageManager pm = getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Toast.makeText(this, "Camera app not present on this device", Toast.LENGTH_SHORT).show();
return;
}
// The device has a camera app ... so use it.
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent,CAMERA_RESULT);
savePhoto.setEnabled(true);
}
This requires an import statement for the Intent class:
import android.content.Intent;
Also necessary is a definition of the constant CAMERA_RESULT. The value 5 has been chosen arbitrarily.
private static final int CAMERA_RESULT = 5;
public static final String EXTRA_PHOTO_FILENAME = "org.wit.myrent.photo.filename";
Next we define the method onPictureTaken. We are chosing the png format as this is the only format acceptable to the helper method writeBitMap.
private void onPictureTaken(Bitmap data) {
String filename = UUID.randomUUID().toString() + ".png";
if (writeBitmap(this, filename, data) == true) {
Intent intent = new Intent();
intent.putExtra(EXTRA_PHOTO_FILENAME, filename);
setResult(Activity.RESULT_OK, intent);
}
else {
setResult(Activity.RESULT_CANCELED);
}
finish();
}
This requires these import statements:
import android.graphics.Bitmap;
import static org.wit.android.helpers.FileIOHelper.writeBitmap;
We must also define the string EXTRA_PHOTO_FILENAME. Note that the arguments in Intent.putExtra comprise key-value pairs, the first argument always being a String.
public static final String EXTRA_PHOTO_FILENAME = "org.wit.myrent.photo.filename";
A further instance variable is required to hold the picture in bitmap form:
private Bitmap residencePhoto;
We retrieve the photo taken by the device camera in the onActivityResult method:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode)
{
case ResidenceCameraActivity.CAMERA_RESULT :
if(data != null) {
processImage(data);
}
else {
Toast.makeText(this, "Camera failure: check simulated camera present emulator advanced settings",
Toast.LENGTH_LONG).show();
}
break;
}
}
For convenience, we have located the code in a private method, processImage:
private void processImage(Intent data)
{
residencePhoto = (Bitmap) data.getExtras().get("data");
if(residencePhoto == null)
{
Toast.makeText(this, "Attempt to take photo did not succeed", Toast.LENGTH_SHORT).show();
}
residenceImage.setImageBitmap(residencePhoto);
}
Complete set import statements:
import org.wit.myrent.R;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
Residence Fragment
Here we provide instructions on how to wire up the camera module to the ResidenceFragment class.
Add imports:
import static org.wit.android.helpers.CameraHelper.showPhoto;
import android.widget.ImageView;
Add a static constant to represent the photo:
private static final int REQUEST_PHOTO = 0;
Create instance variables:
private ImageView cameraButton;
private ImageView photoView;
Invoke the showPhoto method in onStart.
- Its purpose is to display the photo in the thumbnail if a photo exists.
showPhoto(getActivity(), residence, photoView);
Here, for reference, is the refactored onStart method:
@Override
public void onStart()
{
super.onStart();
//display thumbnail photo
showPhoto(getActivity(), residence, photoView);
}
In addListeners:
- Bind the camera button and thumbnail placeholder to their respective layouts:
cameraButton = (ImageView) v.findViewById(R.id.camera_button);
photoView = (ImageView) v.findViewById(R.id.myrent_imageView);
- Register listener for camera button:
cameraButton.setOnClickListener(this);
In onActivityResult introduce code to:
- capture the photo filename,
- save this to the model and
- invoke a method to display the photo in a thumbnail.
Observe that we are replacing the if-else structure with a switch statement:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (resultCode != Activity.RESULT_OK)
{
return;
}
switch (requestCode)
{
case REQUEST_CONTACT:
String name = ContactHelper.getContact(getActivity(), data);
residence.tenant = name;
tenantButton.setText(name);
break;
case REQUEST_PHOTO:
String filename = data.getStringExtra(ResidenceCameraActivity.EXTRA_PHOTO_FILENAME);
if (filename != null)
{
residence.photo = filename;
showPhoto(getActivity(), residence, photoView );
}
break;
}
}
Respond to camera button click: add this code to onClick:
case R.id.camera_button:
Intent ic = new Intent(getActivity(), ResidenceCameraActivity.class);
startActivityForResult(ic, REQUEST_PHOTO);
break;
Gallery
Add a new layout file for the gallery:
File: res/layout/residence_gallery.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/FrameLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="org.wit.myrent.activities.ResidenceCameraActivity" >
<ImageView
android:id="@+id/residenceGalleryImage"
android:layout_width="match_parent"
android:layout_height="426dp"
android:src="@mipmap/ic_launcher" />
</FrameLayout>
Here is the activity code:
File: org.wit.myrent.activities/ResidenceGalleryActivity.java
package org.wit.myrent.activities;
import org.wit.myrent.R;
import org.wit.myrent.app.MyRentApp;
import org.wit.myrent.models.Portfolio;
import org.wit.myrent.models.Residence;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.ImageView;
import static org.wit.android.helpers.CameraHelper.showPhoto;
public class ResidenceGalleryActivity extends AppCompatActivity
{
public static final String EXTRA_PHOTO_FILENAME = "org.wit.myrent.photo.filename";
private ImageView photoView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.residence_gallery);
photoView = (ImageView) findViewById(R.id.residenceGalleryImage);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
showPicture();
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home : onBackPressed();
return true;
default : return super.onOptionsItemSelected(item);
}
}
private void showPicture()
{
Long resId = (Long)getIntent().getSerializableExtra(ResidenceFragment.EXTRA_RESIDENCE_ID);
MyRentApp app = (MyRentApp) getApplication();
Portfolio portfolio = app.portfolio;
Residence residence = portfolio.getResidence(resId);
showPhoto(this, residence, photoView);
}
}
The gallery activity is started by a long-press of the thumbnail in the residence fragment. Here are the necessary changes to ResidenceFragment:
- Add an
OnLongClickListener
interface to those already implemented, the purpose being to listen for a long-press on the thumbnail image.
public class ResidenceFragment extends Fragment implements
TextWatcher,
OnCheckedChangeListener,
OnClickListener,
DatePickerDialog.OnDateSetListener,
View.OnLongClickListener
- Register the listener in the addListeners method:
photoView.setOnLongClickListener(this);
- Implement the long-press listener method:
/* ====================== longpress thumbnail ===================================*/
/*
* Long press the bitmap image to view photo in single-photo gallery
*/
@Override
public boolean onLongClick(View v)
{
Intent i = new Intent(getActivity(), ResidenceGalleryActivity.class);
i.putExtra(EXTRA_RESIDENCE_ID, residence.id);
startActivity(i);
return true;
}
ResidenceFragment
For reference, here is the refactored class:
package org.wit.myrent.activities;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.wit.android.helpers.ContactHelper;
import org.wit.android.helpers.MapHelper;
import org.wit.myrent.R;
import org.wit.myrent.app.MyRentApp;
import org.wit.myrent.models.Portfolio;
import org.wit.myrent.models.Residence;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.app.DatePickerDialog;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.DatePicker;
import android.widget.EditText;
import static org.wit.android.helpers.ContactHelper.sendEmail;
import static org.wit.android.helpers.IntentHelper.navigateUp;
import static org.wit.android.helpers.IntentHelper.startActivityWithData;
import static org.wit.android.helpers.CameraHelper.showPhoto;
import android.widget.ImageView;
public class ResidenceFragment extends Fragment implements TextWatcher,
OnCheckedChangeListener,
OnClickListener,
DatePickerDialog.OnDateSetListener,
View.OnLongClickListener
{
public static final String EXTRA_RESIDENCE_ID = "myrent.RESIDENCE_ID";
private static final int REQUEST_CONTACT = 1;
private static final int REQUEST_PHOTO = 0;
private EditText geolocation;
private CheckBox rented;
private Button dateButton;
private Button tenantButton;
private Button reportButton;
private Residence residence;
private Portfolio portfolio;
String emailAddress = "";
private ImageView cameraButton;
private ImageView photoView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
UUID resId = (UUID) getActivity().getIntent().getSerializableExtra(EXTRA_RESIDENCE_ID);
MyRentApp app = (MyRentApp) getActivity().getApplication();
portfolio = app.portfolio;
residence = portfolio.getResidence(resId);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreateView(inflater, parent, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_residence, parent, false);
addListeners(v);
updateControls(residence);
FloatingActionButton fab = (FloatingActionButton) v.findViewById(R.id.fab);
fab.setOnClickListener(this);
return v;
}
private void addListeners(View v) {
geolocation = (EditText) v.findViewById(R.id.geolocation);
dateButton = (Button) v.findViewById(R.id.registration_date);
rented = (CheckBox) v.findViewById(R.id.isrented);
tenantButton = (Button) v.findViewById(R.id.tenant);
reportButton = (Button) v.findViewById(R.id.residence_reportButton);
cameraButton = (ImageView) v.findViewById(R.id.camera_button);
photoView = (ImageView) v.findViewById(R.id.myrent_imageView);
geolocation.addTextChangedListener(this);
dateButton.setOnClickListener(this);
rented.setOnCheckedChangeListener(this);
tenantButton.setOnClickListener(this);
reportButton.setOnClickListener(this);
cameraButton.setOnClickListener(this);
photoView.setOnLongClickListener(this);
}
public void updateControls(Residence residence) {
geolocation.setText(residence.geolocation);
rented.setChecked(residence.rented);
dateButton.setText(residence.getDateString());
}
@Override
public void onStart() {
super.onStart();
//display thumbnail photo
showPhoto(getActivity(), residence, photoView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
navigateUp(getActivity());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onPause() {
super.onPause();
portfolio.saveResidences();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
switch (requestCode) {
case REQUEST_CONTACT:
String name = ContactHelper.getContact(this, data);
emailAddress = ContactHelper.getEmail(this, data);
tenantButton.setText(name + " : " + emailAddress);
residence.tenant = name;
break;
case REQUEST_PHOTO:
String filename = data.getStringExtra(ResidenceCameraActivity.EXTRA_PHOTO_FILENAME);
if (filename != null) {
residence.photo = filename;
showPhoto(getActivity(), residence, photoView);
}
break;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable c) {
Log.i(this.getClass().getSimpleName(), "geolocation " + c.toString());
residence.geolocation = c.toString();
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
residence.rented = isChecked;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.registration_date:
Calendar c = Calendar.getInstance();
DatePickerDialog dpd = new DatePickerDialog(getActivity(), this, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
dpd.show();
break;
case R.id.tenant:
Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(i, REQUEST_CONTACT);
if (residence.tenant != null) {
tenantButton.setText("Tenant: " + residence.tenant);
}
break;
case R.id.residence_reportButton:
sendEmail(getActivity(), "emailAddress", getString(R.string.residence_report_subject), residence.getResidenceReport(getActivity()));
break;
case R.id.fab:
startActivityWithData(getActivity(), MapActivity.class, EXTRA_RESIDENCE_ID, residence.id);
break;
case R.id.camera_button:
Intent ic = new Intent(getActivity(), ResidenceCameraActivity.class);
startActivityForResult(ic, REQUEST_PHOTO);
break;
}
}
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
Date date = new GregorianCalendar(year, monthOfYear, dayOfMonth).getTime();
residence.date = date;
dateButton.setText(residence.getDateString());
}
/* ====================== longpress thumbnail ===================================*/
/*
* Long press the bitmap image to view photo in single-photo gallery
*/
@Override
public boolean onLongClick(View v)
{
Intent i = new Intent(getActivity(), ResidenceGalleryActivity.class);
i.putExtra(EXTRA_RESIDENCE_ID, residence.id);
startActivity(i);
return true;
}
}
Test
Test the app as follows:
- Build and install on device or emulator
- Create a new residence
- Click the photo button
- Take a photo
- Save the photo
- Verify the photo in thumbnail in details view
- Long-press the thumbnail: does photo display in gallery?
- Return from gallery to details view using up button.
- Switch to residence list and back to details view: is picture still displayed?
- Shutdown the app and relaunch: is picture still displayed in thumbnail?
The application at the end of this lab is available for reference here: myrent-15
Camera Permissions
This file contains the gist of the code required to determine if permission to use the camera is to be allowed or denied at runtime.
package org.wit.mytweet.activities;
import android.support.v4.app.ActivityCompat;
...
...
public class MyTweetCameraActivity extends AppCompatActivity implements View.OnClickListener {
public static final int CAMERA_PERMISSION = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
@Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.takePhoto:
/*-- jf 24-12-2015--*/
checkCameraPermission();
//onTakePhotoClicked(v);
break;
...
...
}
}
public void onTakePhotoClicked() /*-- jf 24-12-2015-- removed redundant parameter*/
{
...
...
}
/*-- jf 24-12-2015--*/
/**
* http://stackoverflow.com/questions/32714787/android-m-permissions-onrequestpermissionsresult-not-being-called
* This is an override of AppCompat.onRequestPermissionsResult
*
* @param requestCode
* @param permissions String array of permissions requested.
* @param grantResults int array of results for permissions request.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case MyTweetCameraActivity.CAMERA_RESULT : {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onTakePhotoClicked();
}
break;
}
}
}
/**
* Bespoke method to check if camera permission exists.
* If it exists then the photo taken.
* Otherwise, the method AppCompat.request permissions is invoked and
* The response is via the callback onRequestPermissionsResult.
* In onRequestPermissionsResult, on successfully being granted permission photo taken.
* See: https://developer.android.com/training/permissions/requesting.html
* See: http://bit.ly/2hBUAZD
*/
private void checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
onTakePhotoClicked();
}
else {
// Invoke callback to request user-granted permission
ActivityCompat.requestPermissions( // If this file represents fragment replace ActivityCompat with android.support.v13.app.FragmentCompat;
new String[]{Manifest.permission.CAMERA},
CAMERA_PERMISSION);
}
}
/*-- jf 24-12-2015--end--*/
}
Exercises
- When a residence is deleted, delete its locally-saved photo (png) if it exists.
Challenges
When a photo is taken and saved:
- save it to the local SQLite database.
- save it to the server. See stackoverflow: Retrofit API to retrieve a png image.
When a residence is deleted, delete the photo on the server.