Java tutorial
/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Credit Udacity lesson ud845-Pets-lesson-four for the primary components and Forum Mentor on Github * cavi2016 project Inventory 10 image retrieval and saving to file. */ package us.theparamountgroup.android.inventory; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.LoaderManager; import android.content.ContentValues; import android.content.Context; import android.content.CursorLoader; import android.content.DialogInterface; import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.ThumbnailUtils; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.support.v4.app.ActivityCompat; import android.support.v4.app.NavUtils; import android.support.v4.content.ContextCompat; import android.support.v4.content.FileProvider; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.theparamountgroup.android.inventory.R; import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import us.theparamountgroup.android.inventory.data.ShellContract; import static us.theparamountgroup.android.inventory.data.DbBitmapUtility.getBytes; /** * Allows user to create a new shell or edit an existing one. */ public class EditorActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String LOG_TAG = EditorActivity.class.getSimpleName(); private static final int PICK_IMAGE_REQUEST = 0; private static final int REQUEST_IMAGE_CAPTURE = 1; // private static final String CAMERA_DIR = "/dcim/"; private static final int MY_PERMISSIONS_REQUEST = 2; /** * Identifier for the shell data loader */ private static final int EXISTING_SHELL_LOADER = 0; /** * constants for image requests */ // a static variable to get a reference of our application context public static Context contextOfApplication; String mCurrentPhotoPath; /** * Content URI for the existing shell (null if it's a new shell) */ private Uri mCurrentShellUri; /** * EditText field to enter the shell's name */ private EditText mNameEditText; /** * EditText field to enter the shell's color */ private EditText mColorEditText; /** * EditText field to enter if the shell has a hole */ private Spinner mHoleSpinner; /** * EditText field to enter the type shell */ private Spinner mTypeSpinner; /** * If the shell has hole. The possible valid values are in the ShellContract.java file: * {@link ShellContract.ShellEntry#HOLE_UNKNOWN}, {@link ShellContract.ShellEntry#HOLE}, or * {@link ShellContract.ShellEntry#NO_HOLE}. */ private int mHole = ShellContract.ShellEntry.HOLE_UNKNOWN; /** * Type of Shell. The possible valid values are in the ShellContract.java file: * {@link ShellContract.ShellEntry#TYPE_SCALLOP}, {@link ShellContract.ShellEntry#TYPE_JINGLE}, * {@link ShellContract.ShellEntry#TYPE_SLIPPER},{@link ShellContract.ShellEntry#TYPE_SHARD}. */ private int mType = ShellContract.ShellEntry.TYPE_SCALLOP; /** * Boolean flag that keeps track of whether the shell has been edited (true) or not (false) */ private boolean mShellHasChanged = false; private ImageView mImageView; private Button mButtonTakePicture; private Uri mUri; private Bitmap mBitmap; // private boolean isGalleryPicture = false; private TextView mQuantityTextView; private EditText mPriceEditText; /** * OnTouchListener that listens for any user touches on a View, implying that they are modifying * the view, and we change the mShellHasChanged boolean to true. */ private View.OnTouchListener mTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { mShellHasChanged = true; return false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_editor); // Examine the intent that was used to launch this activity, // in order to figure out if we're creating a new shell or editing an existing one. Intent intent = getIntent(); mCurrentShellUri = intent.getData(); // If the intent DOES NOT contain a shell content URI, then we know that we are // creating a new shell. if (mCurrentShellUri == null) { // This is a new shell, so change the app bar to say "Add a Shell" setTitle(getString(R.string.editor_activity_title_new_pet)); // Invalidate the options menu, so the "Delete" menu option can be hidden. // (It doesn't make sense to delete a shell that hasn't been created yet.) invalidateOptionsMenu(); } else { // Otherwise this is an existing shell, so change app bar to say "Edit Shell" setTitle(getString(R.string.editor_activity_title_edit_pet)); // Initialize a loader to read the shell data from the database // and display the current values in the editor getLoaderManager().initLoader(EXISTING_SHELL_LOADER, null, this); } // Find all relevant views that we will need to read user input from mNameEditText = (EditText) findViewById(R.id.edit_shell_name); mColorEditText = (EditText) findViewById(R.id.edit_shell_color); mHoleSpinner = (Spinner) findViewById(R.id.spinner_hole); mTypeSpinner = (Spinner) findViewById(R.id.spinner_type); mQuantityTextView = (TextView) findViewById(R.id.edit_product_quantity); mPriceEditText = (EditText) findViewById(R.id.price_enter); // Setup OnTouchListeners on all the input fields, so we can determine if the user // has touched or modified them. This will let us know if there are unsaved changes // or not, if the user tries to leave the editor without saving. mNameEditText.setOnTouchListener(mTouchListener); mColorEditText.setOnTouchListener(mTouchListener); mHoleSpinner.setOnTouchListener(mTouchListener); mTypeSpinner.setOnTouchListener(mTouchListener); mQuantityTextView.setOnTouchListener(mTouchListener); mPriceEditText.setOnTouchListener(mTouchListener); /* The view tree observer can be used to get notifications when global events, like layout, happen. * The returned ViewTreeObserver observer is not guaranteed to remain valid for the lifetime of this View. */ mImageView = (ImageView) findViewById(R.id.image); ViewTreeObserver viewTreeObserver = mImageView.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Log.i(LOG_TAG, " in onGlobalLayout trying to get image to appear"); mImageView.setImageResource(R.drawable.ic_soul_shells_logo); mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this); } }); // assign TAKE PHOTO button to mButtonTakePicture variable. mButtonTakePicture = (Button) findViewById(R.id.take_photo); mButtonTakePicture.setEnabled(false); // ask permission to use the camera and image gallery requestPermissions(); setupHoleSpinner(); setupTypeSpinner(); } /** * Setup the dropdown spinner that allows the user to select if the shell has a hole. */ private void setupHoleSpinner() { // Create adapter for spinner. The list options are from the String array it will use // the spinner will use the default layout ArrayAdapter holeSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.array_hole_options, android.R.layout.simple_spinner_item); // Specify dropdown layout style - simple list view with 1 item per line holeSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line); // Apply the adapter to the spinner mHoleSpinner.setAdapter(holeSpinnerAdapter); // Set the integer mSelected to the constant values mHoleSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String selection = (String) parent.getItemAtPosition(position); if (!TextUtils.isEmpty(selection)) { if (selection.equals(getString(R.string.no_hole))) { mHole = ShellContract.ShellEntry.HOLE; } else if (selection.equals(getString(R.string.hole))) { mHole = ShellContract.ShellEntry.NO_HOLE; } else { mHole = ShellContract.ShellEntry.HOLE_UNKNOWN; } } } // Because AdapterView is an abstract class, onNothingSelected must be defined @Override public void onNothingSelected(AdapterView<?> parent) { mHole = ShellContract.ShellEntry.HOLE_UNKNOWN; } }); } /** * Setup the dropdown spinner that allows the user to select the type of shell. */ private void setupTypeSpinner() { // Create adapter for spinner. The list options are from the String array it will use // the spinner will use the default layout ArrayAdapter typeSpinnerAdapter = ArrayAdapter.createFromResource(this, R.array.array_type_options, android.R.layout.simple_spinner_item); // Specify dropdown layout style - simple list view with 1 item per line typeSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line); // Apply the adapter to the spinner mTypeSpinner.setAdapter(typeSpinnerAdapter); // Set the integer mSelected to the constant values mTypeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { String selection = (String) parent.getItemAtPosition(position); if (!TextUtils.isEmpty(selection)) { if (selection.equals(getString(R.string.type_scallop))) { mType = ShellContract.ShellEntry.TYPE_SCALLOP; } else if (selection.equals(getString(R.string.type_jingle))) { mType = ShellContract.ShellEntry.TYPE_JINGLE; } else if (selection.equals(getString(R.string.type_slipper))) { mType = ShellContract.ShellEntry.TYPE_SLIPPER; } else { mType = ShellContract.ShellEntry.TYPE_SHARD; } } } // Because AdapterView is an abstract class, onNothingSelected must be defined @Override public void onNothingSelected(AdapterView<?> parent) { mType = ShellContract.ShellEntry.TYPE_SCALLOP; } }); } /** * Get user input from editor and save pet into database. */ private void saveShell() { int THUMBSIZE = 75; byte[] thumbImage = null;// byte array variable used to store in data base String photoString; // Read from input fields // Use trim to eliminate leading or trailing white space and convert to a string String nameString = mNameEditText.getText().toString().trim(); String colorString = mColorEditText.getText().toString().trim(); String quantityString = mQuantityTextView.getText().toString().trim(); String priceString = mPriceEditText.getText().toString().trim(); /* check to see if an image taken with the camera or chosen from the device */ if (mUri != null) { photoString = mUri.toString(); // if there is an image make a corresponding thumbnail bitmap image size 75x75 Bitmap bitThumbImage = ThumbnailUtils.extractThumbnail(mBitmap, THUMBSIZE, THUMBSIZE); // convert thumbnail bitmap to byte array thumbImage = getBytes(bitThumbImage); } else { photoString = ""; } // Check if this is supposed to be a new shell // and check if all the fields in the editor are blank if (mCurrentShellUri == null && TextUtils.isEmpty(nameString) || TextUtils.isEmpty(colorString) || TextUtils.isEmpty(photoString) || TextUtils.isEmpty(priceString) || TextUtils.isEmpty(quantityString)) { // Since no fields were modified, we can return early without creating a new shell. // No need to create ContentValues and no need to do any ContentProvider operations. Toast.makeText(this, "All fields must be filled.\n\n Shell Was Not Saved", Toast.LENGTH_LONG).show(); Intent i = getIntent(); finish(); startActivity(i); return; } int quantity = 0; if (!TextUtils.isEmpty(quantityString)) { quantity = Integer.parseInt(quantityString); } double price = 0.0; if (!TextUtils.isEmpty(priceString)) { price = Double.parseDouble(priceString); } // Create a ContentValues object where column names are the keys, // and pet attributes from the editor are the values. ContentValues values = new ContentValues(); values.put(ShellContract.ShellEntry.COLUMN_SHELL_NAME, nameString); values.put(ShellContract.ShellEntry.COLUMN_SHELL_COLOR, colorString); values.put(ShellContract.ShellEntry.COLUMN_SHELL_HOLE, mHole); values.put(ShellContract.ShellEntry.COLUMN_SHELL_TYPE, mType); values.put(ShellContract.ShellEntry.COLUMN_SHELL_PHOTO, photoString); values.put(ShellContract.ShellEntry.COLUMN_SHELL_QUANTITY, quantity); values.put(ShellContract.ShellEntry.COLUMN_SHELL_PRICE, price); values.put(ShellContract.ShellEntry.COLUMN_SHELL_THUMBNAIL, thumbImage); // Determine if this is a new or existing shell by checking if mCurrentShellUri is null or not if (mCurrentShellUri == null) { // This is a NEW shell, so insert a new shell into the provider, // returning the content URI for the new shell. Uri newUri = getContentResolver().insert(ShellContract.ShellEntry.CONTENT_URI, values); // Show a toast message depending on whether or not the insertion was successful. if (newUri == null) { // If the new content URI is null, then there was an error with insertion. Toast.makeText(this, getString(R.string.editor_insert_pet_failed), Toast.LENGTH_SHORT).show(); } else { // Otherwise, the insertion was successful and we can display a toast. Toast.makeText(this, getString(R.string.editor_insert_pet_successful), Toast.LENGTH_SHORT).show(); } } else { // Otherwise this is an EXISTING shell, so update the shell with content URI: mCurrentShellUri // and pass in the new ContentValues. Pass in null for the selection and selection args // because mCurrentShellUri will already identify the correct row in the database that // we want to modify. int rowsAffected = getContentResolver().update(mCurrentShellUri, values, null, null); // Show a toast message depending on whether or not the update was successful. if (rowsAffected == 0) { // If no rows were affected, then there was an error with the update. Toast.makeText(this, getString(R.string.editor_update_pet_failed), Toast.LENGTH_SHORT).show(); } else { // Otherwise, the update was successful and we can display a toast. Toast.makeText(this, getString(R.string.editor_update_pet_successful), Toast.LENGTH_SHORT).show(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu options from the res/menu/menu_editor.xml file. // This adds menu items to the app bar. getMenuInflater().inflate(R.menu.menu_editor, menu); return true; } /** * This method is called after invalidateOptionsMenu(), so that the * menu can be updated (some menu items can be hidden or made visible). */ @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); // If this is a new shell, hide the "Delete" menu item. if (mCurrentShellUri == null) { MenuItem menuItem = menu.findItem(R.id.action_delete); menuItem.setVisible(false); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // User clicked on a menu option in the app bar overflow menu switch (item.getItemId()) { // Respond to a click on the "Save" menu option case R.id.action_save: // Save shell to database saveShell(); // Exit activity finish(); return true; // Respond to a click on the "Delete" menu option case R.id.action_delete: // Pop up confirmation dialog for deletion showDeleteConfirmationDialog(); return true; // Respond to a click on the "Up" arrow button in the app bar case android.R.id.home: // If the shell hasn't changed, continue with navigating up to parent activity // which is the {@link CatalogActivity}. if (!mShellHasChanged) { NavUtils.navigateUpFromSameTask(EditorActivity.this); return true; } // Otherwise if there are unsaved changes, setup a dialog to warn the user. // Create a click listener to handle the user confirming that // changes should be discarded. DialogInterface.OnClickListener discardButtonClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // User clicked "Discard" button, navigate to parent activity. NavUtils.navigateUpFromSameTask(EditorActivity.this); } }; // Show a dialog that notifies the user they have unsaved changes showUnsavedChangesDialog(discardButtonClickListener); return true; } return super.onOptionsItemSelected(item); } /** * This method is called when the back button is pressed. */ @Override public void onBackPressed() { // If the shell hasn't changed, continue with handling back button press if (!mShellHasChanged) { super.onBackPressed(); return; } // Otherwise if there are unsaved changes, setup a dialog to warn the user. // Create a click listener to handle the user confirming that changes should be discarded. DialogInterface.OnClickListener discardButtonClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // User clicked "Discard" button, close the current activity. finish(); } }; // Show dialog that there are unsaved changes showUnsavedChangesDialog(discardButtonClickListener); } @Override public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { // Since the editor shows all shell attributes, define a projection that contains // all columns from the pet table String[] projection = { ShellContract.ShellEntry._ID, ShellContract.ShellEntry.COLUMN_SHELL_NAME, ShellContract.ShellEntry.COLUMN_SHELL_COLOR, ShellContract.ShellEntry.COLUMN_SHELL_HOLE, ShellContract.ShellEntry.COLUMN_SHELL_TYPE, ShellContract.ShellEntry.COLUMN_SHELL_QUANTITY, ShellContract.ShellEntry.COLUMN_SHELL_PRICE, ShellContract.ShellEntry.COLUMN_SHELL_PHOTO, ShellContract.ShellEntry.COLUMN_SHELL_THUMBNAIL }; // This loader will execute the ContentProvider's query method on a background thread return new CursorLoader(this, // Parent activity context mCurrentShellUri, // Query the content URI for the current shell projection, // Columns to include in the resulting Cursor null, // No selection clause null, // No selection arguments null); // Default sort order } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { // Bail early if the cursor is null or there is less than 1 row in the cursor if (cursor == null || cursor.getCount() < 1) { return; } // Proceed with moving to the first row of the cursor and reading data from it // (This should be the only row in the cursor) if (cursor.moveToFirst()) { // Find the columns of Shell attributes that we're interested in int nameColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_NAME); int breedColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_COLOR); int genderColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_HOLE); int typeColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_TYPE); int photoColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_PHOTO); int quantityColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_QUANTITY); int priceColumnIndex = cursor.getColumnIndex(ShellContract.ShellEntry.COLUMN_SHELL_PRICE); // Extract out the value from the Cursor for the given column index String name = cursor.getString(nameColumnIndex); String color = cursor.getString(breedColumnIndex); int hole = cursor.getInt(genderColumnIndex); int type = cursor.getInt(typeColumnIndex); String photo = cursor.getString(photoColumnIndex); int quantity = cursor.getInt(quantityColumnIndex); double price = cursor.getDouble(priceColumnIndex); // Update the views on the screen with the values from the database mNameEditText.setText(name); mColorEditText.setText(color); mQuantityTextView.setText(Integer.toString(quantity)); mPriceEditText.setText(Double.toString(price)); if (!photo.isEmpty()) { mUri = Uri.parse(photo); mBitmap = getBitmapFromUri(mUri); mImageView.setImageBitmap(mBitmap); } // Hole is a dropdown spinner, so map the constant value from the database // into one of the dropdown options (0 is Unknown, 1 is Hole, 2 is No Hole). // Then call setSelection() so that option is displayed on screen as the current selection. switch (hole) { case ShellContract.ShellEntry.HOLE: mHoleSpinner.setSelection(1); break; case ShellContract.ShellEntry.NO_HOLE: mHoleSpinner.setSelection(2); break; default: mHoleSpinner.setSelection(0); break; } // type is a dropdown spinner, so map the constant value from the database // into one of the dropdown options (0 is Scallop, 1 is Jingle, 2 is Slipper, 3 is Shard). // Then call setSelection() so that option is displayed on screen as the current selection. switch (type) { case ShellContract.ShellEntry.TYPE_SHARD: mTypeSpinner.setSelection(3); break; case ShellContract.ShellEntry.TYPE_JINGLE: mTypeSpinner.setSelection(1); break; case ShellContract.ShellEntry.TYPE_SLIPPER: mTypeSpinner.setSelection(2); break; default: mTypeSpinner.setSelection(0); break; } } } @Override public void onLoaderReset(Loader<Cursor> loader) { // If the loader is invalidated, clear out all the data from the input fields. mNameEditText.setText(""); mColorEditText.setText(""); mHoleSpinner.setSelection(0); // Select "Unknown" hole mTypeSpinner.setSelection(0); // Select "Scallop" to type mQuantityTextView.clearComposingText(); mPriceEditText.clearComposingText(); } /** * Show a dialog that warns the user there are unsaved changes that will be lost * if they continue leaving the editor. * * @param discardButtonClickListener is the click listener for what to do when * the user confirms they want to discard their changes */ private void showUnsavedChangesDialog(DialogInterface.OnClickListener discardButtonClickListener) { // Create an AlertDialog.Builder and set the message, and click listeners // for the positive and negative buttons on the dialog. AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.unsaved_changes_dialog_msg); builder.setPositiveButton(R.string.discard, discardButtonClickListener); builder.setNegativeButton(R.string.keep_editing, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User clicked the "Keep editing" button, so dismiss the dialog // and continue editing the shell. if (dialog != null) { dialog.dismiss(); } } }); // Create and show the AlertDialog AlertDialog alertDialog = builder.create(); alertDialog.show(); } /** * Prompt the user to confirm that they want to delete this shell. */ private void showDeleteConfirmationDialog() { // Create an AlertDialog.Builder and set the message, and click listeners // for the positivee and negative buttons on the dialog. AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.delete_dialog_msg); builder.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User clicked the "Delete" button, so delete the shell. deleteShell(); } }); builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User clicked the "Cancel" button, so dismiss the dialog // and continue editing the shell. if (dialog != null) { dialog.dismiss(); } } }); // Create and show the AlertDialog AlertDialog alertDialog = builder.create(); alertDialog.show(); } /** * Perform the deletion of the shell in the database. */ private void deleteShell() { // Only perform the delete if this is an existing shell. if (mCurrentShellUri != null) { // Call the ContentResolver to delete the shell at the given content URI. // Pass in null for the selection and selection args because the mCurrentShellUri // content URI already identifies the shell that we want. int rowsDeleted = getContentResolver().delete(mCurrentShellUri, null, null); // Show a toast message depending on whether or not the delete was successful. if (rowsDeleted == 0) { // If no rows were deleted, then there was an error with the delete. Toast.makeText(this, getString(R.string.editor_delete_shell_failed), Toast.LENGTH_SHORT).show(); } else { // Otherwise, the delete was successful and we can display a toast. Toast.makeText(this, getString(R.string.editor_delete_pet_successful), Toast.LENGTH_SHORT).show(); } } // Close the activity finish(); } /*********************** This section primary is used obtaining and storing images ****************** * credit Forum Mentor on Github * cavi2016 project Inventory10 for methods used below. */ // Request permission to read/write external storage public void requestPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { } else { ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, MY_PERMISSIONS_REQUEST); } } else { mButtonTakePicture.setEnabled(true); } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { mButtonTakePicture.setEnabled(true); } else { Toast.makeText(getApplicationContext(), "Permission Denied", Toast.LENGTH_SHORT).show(); } return; } } } /* Take picture using camera and save to file using MediaStore. Image location uri stored in variable mUri*/ public void takePicture(View view) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Log.i(LOG_TAG, "in takePicture"); try { File file = createImageFile(); Log.i(LOG_TAG, "in takePicture just back from createImagefile"); mUri = FileProvider.getUriForFile(getApplication().getApplicationContext(), "us.theparamountgroup.android.inventory.fileprovider", file); intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri); startActivityForResult(intent, REQUEST_IMAGE_CAPTURE); } catch (IOException e) { e.printStackTrace(); } } /* select an image already on device */ public void openImageSelector(View view) { Intent intent; Log.e(LOG_TAG, "While is set and the ifs are worked through."); if (Build.VERSION.SDK_INT < 19) { intent = new Intent(Intent.ACTION_GET_CONTENT); } else { intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); } Log.e(LOG_TAG, "Check write to external permissions"); intent.setType("image/*"); startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE_REQUEST); } // create file to store image private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera"); File image = File.createTempFile(imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); // Save a file: path for use with ACTION_VIEW intents mCurrentPhotoPath = "file:" + image.getAbsolutePath(); Log.i(LOG_TAG, "in createImageFile mCurrentPhotoPath: " + mCurrentPhotoPath); return image; } /* onActivityResult getBitmapFromUri assign to the mImageView in the Editor and scale * to fit in the available space. */ @Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { Log.i(LOG_TAG, "Received an \"Activity Result\""); if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) { if (resultData != null) { mUri = resultData.getData(); Log.i(LOG_TAG, "Uri: " + mUri.toString()); mBitmap = getBitmapFromUri(mUri); mImageView.setImageBitmap(mBitmap); mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); } } else if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) { Log.i(LOG_TAG, "Uri: " + mUri.toString()); mBitmap = getBitmapFromUri(mUri); mImageView.setImageBitmap(mBitmap); mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); } } /* getBitmapFromUri method is called return a bitmap image using the passed Uri. * * */ private Bitmap getBitmapFromUri(Uri uri) { if (uri == null) { return null; } int targetW = mImageView.getWidth(); int targetH = mImageView.getHeight(); ParcelFileDescriptor parcelFileDescriptor = null; try { parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, opts); int photoW = opts.outWidth; int photoH = opts.outHeight; int scaleFactor = Math.min(photoW / targetW, photoH / targetH); opts.inJustDecodeBounds = false; opts.inSampleSize = scaleFactor; opts.inPurgeable = true; Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, opts); return image; } catch (Exception e) { Log.e(LOG_TAG, "Failed to load image.", e); return null; } finally { try { if (parcelFileDescriptor != null) { parcelFileDescriptor.close(); } } catch (IOException e) { e.printStackTrace(); Log.e(LOG_TAG, "Error closing ParcelFile Descriptor"); } } } /* orderProduct will send users to the website www.soulshells.com where they * can order their own shell necklace */ public void orderProduct(View view) { String nameProduct = mNameEditText.getText().toString(); Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setData(Uri.parse("mailto:")); // only email apps should handle this intent.putExtra(Intent.EXTRA_EMAIL, "supplier@example.com"); intent.putExtra(Intent.EXTRA_SUBJECT, "Order " + nameProduct); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(this, "Sorry no access to email", Toast.LENGTH_SHORT).show(); return; } } /* addQuantity using button in editor */ public void addQuantity(View view) { int quantity; String quantityString = mQuantityTextView.getText().toString(); if (quantityString.isEmpty()) { quantity = 0; } else { quantity = Integer.parseInt(quantityString); } quantity = quantity + 1; mQuantityTextView.setText(String.valueOf(quantity)); } /* subtractQuantity using button in editor */ public void subtractQuantity(View view) { int quantity; String quantityString = mQuantityTextView.getText().toString(); if (quantityString.isEmpty()) { quantity = 0; } else { quantity = Integer.parseInt(quantityString); } if (quantity > 0) { quantity = quantity - 1; } mQuantityTextView.setText(String.valueOf(quantity)); } }