<!-- message for the dialog showing the camera is disabled because of security policies. Camera cannot be used. -->
<string name="camera_disabled">Camera has been disabled because of security policies.</string>
+ <!-- message for the dialog showing that the user's photo could not be saved [CHAR LIMIT=NONE] -->
+ <string name="media_storage_failure">There was a problem saving your photo or video.</string>
+
+ <!-- Reason used for session failure which is visible in UI [CHAR LIMIT=NONE] -->
+ <string name="reason_storage_failure">Photo storage failure.</string>
+
<!-- alert to the user to wait for some operation to complete -->
<string name="wait">Please wait\u2026</string>
<!-- A label that overlays on top of the preview frame to indicate the camcorder is in time lapse mode [CHAR LIMIT=35] -->
<string name="time_lapse_title">Time lapse recording</string>
+ <!-- Default feedback that is entered in the Feedback textview for issues related to camera access. [CHAR LIMIT=NONE] -->
+ <string name="feedback_description_camera_access">The app couldn\'t connect to the camera</string>
+
+ <!-- Default feedback that is entered in the Feedback textview for issues related to saving photos. [CHAR LIMIT=NONE] -->
+ <string name="feedback_description_save_photo">Photo or video did not save to the device.</string>
+
<!-- Screen display message during image capture to indicate that the capture is in progress, like during HDR+. [CHAR LIMIT=20] -->
<string name="capturing">Capturing</string>
eventprotos.CameraFailure.FailureReason.SECURITY, null,
UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera disabled: " + cameraId);
- CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
+ CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
}
@Override
eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera open failure: " + info);
- CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+ CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
}
@Override
public void onDeviceOpenedAlready(int cameraId, String info) {
Log.w(TAG, "Camera open already: " + cameraId + "," + info);
- CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+ CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
}
@Override
eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera reconnection failure:" + info);
- CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+ CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
}
private static class MainHandler extends Handler {
updateSessionProgress(0);
showProcessError(reason);
}
+ if (reason.equals("content")) {
+ UsageStatistics.instance().storageWarning(Storage.ACCESS_FAILURE);
+ CameraUtil.showError(CameraActivity.this, R.string.media_storage_failure,
+ R.string.feedback_description_save_photo, false);
+ }
+
// HERE
mDataAdapter.refresh(uri);
}
Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
finish();
} else {
- CameraUtil.showErrorAndFinish(CameraActivity.this,
- R.string.cannot_connect_camera);
+ CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
+ R.string.feedback_description_camera_access, true);
}
}
};
// Log error and continue. Modules requiring OneCamera should check
// and handle if null by showing error dialog or other treatment.
Log.e(TAG, "Creating camera manager failed.", e);
- CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+ CameraUtil.showError(this, R.string.camera_disabled, R.string.feedback_description_camera_access, true);
}
profile.mark("OneCameraManager.get");
mCameraController = new CameraController(mAppContext, this, mMainHandler,
@Override
public void onCameraAccessException() {
- CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera);
+ CameraUtil.showError(CameraActivity.this, R.string.camera_disabled,
+ R.string.feedback_description_camera_access, true);
}
});
profile.stop();
@Override
public void showErrorAndFinish(int messageId) {
- CameraUtil.showErrorAndFinish(this, messageId);
+ CameraUtil.showError(this, messageId, R.string.feedback_description_camera_access, true);
}
@Override
import com.android.camera.exif.ExifInterface;
import java.io.File;
+import java.io.IOException;
/**
* A class implementing {@link com.android.camera.app.MediaSaver}.
width = options.outWidth;
height = options.outHeight;
}
- return Storage.addImage(
- resolver, title, date, loc, orientation, exif, data, width, height,
- mimeType);
+ try {
+ return Storage.addImage(
+ resolver, title, date, loc, orientation, exif, data, width, height,
+ mimeType);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write data", e);
+ return null;
+ }
}
@Override
protected void onPostExecute(Uri uri) {
- if (listener != null && uri != null) {
+ if (listener != null) {
listener.onMediaSaved(uri);
}
boolean previouslyFull = isQueueFull();
import java.util.List;
import java.util.Vector;
+
public class PhotoModule
extends CameraModule
implements PhotoController,
private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
new MediaSaver.OnMediaSavedListener() {
+
@Override
public void onMediaSaved(Uri uri) {
if (uri != null) {
mActivity.notifyNewMedia(uri);
+ } else {
+ onError(false);
}
}
};
+
+ /**
+ * Displays Feedback dialog on non-fatal errors
+ * @param finishActivity indicates whether to finish the activity after the user submits Feedback
+ */
+ private void onError(boolean finishActivity) {
+ UsageStatistics.instance().storageWarning(Storage.ACCESS_FAILURE);
+ CameraUtil.showError(mActivity, R.string.media_storage_failure,
+ R.string.feedback_description_save_photo, finishActivity);
+ }
+
private boolean mShouldResizeTo16x9 = false;
/**
mActivity.setResultEx(Activity.RESULT_OK);
mActivity.finish();
} catch (IOException ex) {
- Log.w(TAG, "exception saving result to URI: " + mSaveUri, ex);
- // ignore exception
+ onError(true);
} finally {
CameraUtil.closeSilently(outputStream);
}
} catch (FileNotFoundException ex) {
Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
mActivity.setResultEx(Activity.RESULT_CANCELED);
- mActivity.finish();
+ onError(true);
return;
} catch (IOException ex) {
Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
mActivity.setResultEx(Activity.RESULT_CANCELED);
- mActivity.finish();
+ onError(true);
return;
} finally {
CameraUtil.closeSilently(tempStream);
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.MediaColumns;
import android.util.LruCache;
-
import com.android.camera.data.FilmstripItemData;
import com.android.camera.debug.Log;
import com.android.camera.exif.ExifInterface;
import com.android.camera.util.Size;
import com.google.common.base.Optional;
+import javax.annotation.Nonnull;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import javax.annotation.Nonnull;
-
public class Storage {
public static final String DCIM =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
public static final long UNAVAILABLE = -1L;
public static final long PREPARING = -2L;
public static final long UNKNOWN_SIZE = -3L;
+ public static final long ACCESS_FAILURE = -4L;
public static final long LOW_STORAGE_THRESHOLD_BYTES = 50000000;
public static final String CAMERA_SESSION_SCHEME = "camera_session";
private static final Log.Tag TAG = new Log.Tag("Storage");
*/
public static Uri addImage(ContentResolver resolver, String title, long date,
Location location, int orientation, ExifInterface exif, byte[] jpeg, int width,
- int height) {
+ int height) throws IOException {
return addImage(resolver, title, date, location, orientation, exif, jpeg, width, height,
FilmstripItemData.MIME_TYPE_JPEG);
*/
public static Uri addImage(ContentResolver resolver, String title, long date,
Location location, int orientation, ExifInterface exif, byte[] data, int width,
- int height, String mimeType) {
+ int height, String mimeType) throws IOException {
String path = generateFilepath(title, mimeType);
long fileLength = writeFile(path, data, exif);
*/
public static Uri updateImage(Uri imageUri, ContentResolver resolver, String title, long date,
Location location, int orientation, ExifInterface exif,
- byte[] jpeg, int width, int height, String mimeType) {
+ byte[] jpeg, int width, int height, String mimeType) throws IOException {
String path = generateFilepath(title, mimeType);
writeFile(path, jpeg, exif);
return updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path,
*
* @return The size of the file. -1 if failed.
*/
- public static long writeFile(String path, byte[] jpeg, ExifInterface exif) {
+ public static long writeFile(String path, byte[] jpeg, ExifInterface exif) throws IOException {
if (!createDirectoryIfNeeded(path)) {
Log.e(TAG, "Failed to create parent directory for file: " + path);
return -1;
}
if (exif != null) {
- try {
exif.writeExif(jpeg, path);
File f = new File(path);
return f.length();
- } catch (Exception e) {
- Log.e(TAG, "Failed to write data", e);
- }
} else {
return writeFile(path, jpeg);
}
- return -1;
+// return -1;
}
/**
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.SparseIntArray;
+import com.android.camera.debug.Log;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
// IFD Interoperability tags
public static final int TAG_INTEROPERABILITY_INDEX =
defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+ private static final Log.Tag TAG = new Log.Tag("ExifInterface");
/**
* Tags that contain offset markers. These are included in the banned
throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
}
InputStream is = null;
- try {
- is = new BufferedInputStream(new FileInputStream(inFileName));
- readExif(is);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
- }
+
+ is = new BufferedInputStream(new FileInputStream(inFileName));
+ readExif(is);
+
is.close();
}
throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
}
OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- s.write(jpeg, 0, jpeg.length);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
+ s = getExifWriterStream(exifOutFileName);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
s.close();
}
throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
}
OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
+
+ s = getExifWriterStream(exifOutFileName);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
s.close();
}
throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
}
OutputStream s = null;
- try {
- s = getExifWriterStream(exifOutFileName);
- doExifStreamIO(jpegStream, s);
- s.flush();
- } catch (IOException e) {
- closeSilently(s);
- throw e;
- }
+
+ s = getExifWriterStream(exifOutFileName);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+
s.close();
}
throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
}
InputStream is = null;
- try {
- is = new FileInputStream(jpegFileName);
- writeExif(is, exifOutFileName);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
- }
+
+ is = new FileInputStream(jpegFileName);
+ writeExif(is, exifOutFileName);
+
is.close();
}
// Attempt to overwrite tag values without changing lengths (avoids
// file copy).
ret = rewriteExif(buf, tags);
- } catch (IOException e) {
- closeSilently(file);
- throw e;
} finally {
closeSilently(is);
}
readExif(imageBytes);
setTags(tags);
writeExif(imageBytes, filename);
- } catch (IOException e) {
- closeSilently(is);
- throw e;
} finally {
is.close();
// Prevent clobbering of mData
import com.android.camera.processing.imagebackend.ImageToProcess;
import com.android.camera.processing.imagebackend.TaskImageContainer;
import com.android.camera.session.CaptureSession;
+
import com.google.common.base.Optional;
import com.google.common.util.concurrent.ListenableFuture;
private final MainThread mExecutor;
private final ImageRotationCalculator mImageRotationCalculator;
private final ImageBackend mImageBackend;
-
+
/**
* Constructor
*
orientation, exif, listener);
return;
}
- mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
- orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
+ try {
+ mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
+ orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException", e);
+ // TODO: Replace with a sensisble description
+ // Placeholder string R.string.reason_storage_failure
+ finishWithFailure("content");
+ }
mSessionNotifier.notifyTaskDone(mUri);
}
import com.android.camera.util.Size;
import com.google.common.base.Optional;
+import java.io.IOException;
+
/**
* Handles placeholders in filmstrip that show up temporarily while a final
* output media item is being produced.
* @return The content URI of the new media item.
*/
public Uri finishPlaceholder(Session session, Location location, int orientation,
- ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) {
+ ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) throws IOException {
Uri resultUri = Storage.updateImage(session.outputUri, mContext.getContentResolver(),
session.outputTitle, session.time, location, orientation, exif, jpeg, width,
height, mimeType);
package com.android.camera.session;
import android.net.Uri;
-
import com.android.camera.exif.ExifInterface;
/**
import com.android.camera.exif.ExifInterface;
import java.io.File;
+import java.io.IOException;
/**
* Default implementation of the {@link StackSaver} interface. It creates a
@Override
public Uri saveStackedImage(byte[] data, String title, int width, int height,
- int imageOrientation, ExifInterface exif, long captureTimeEpoch, String mimeType) {
+ int imageOrientation, ExifInterface exif, long captureTimeEpoch, String mimeType) {
if (exif != null) {
exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, imageOrientation));
}
Storage.generateFilepath(mStackDirectory.getAbsolutePath(), title, mimeType);
Log.d(TAG, "Saving using stack image saver: " + filePath);
- long fileLength = Storage.writeFile(filePath, data, exif);
+ long fileLength = 0;
+ try {
+ fileLength = Storage.writeFile(filePath, data, exif);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
if (fileLength >= 0) {
return Storage.addImageToMediaStore(mContentResolver, title, captureTimeEpoch,
mGpsLocation, imageOrientation, fileLength, filePath, width, height, mimeType);
* Shows custom error dialog. Designed specifically
* for the scenario where the camera cannot be attached.
*/
- public static void showErrorAndFinish(final Activity activity, int msgId) {
- DialogInterface.OnClickListener buttonListener =
+ public static void showError(final Activity activity, final int dialogMsgId, final int feedbackMsgId,
+ final boolean finishActivity) {
+ final DialogInterface.OnClickListener buttonListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- activity.finish();
+ if (finishActivity) {
+ activity.finish();
+ }
}
};
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- new GoogleHelpHelper(activity).sendGoogleFeedback();
- activity.finish();
+ new GoogleHelpHelper(activity).sendGoogleFeedback(feedbackMsgId);
+ if (finishActivity) {
+ activity.finish();
+ }
}
};
TypedValue out = new TypedValue();
new AlertDialog.Builder(activity)
.setCancelable(false)
.setTitle(R.string.camera_error_title)
- .setMessage(msgId)
+ .setMessage(dialogMsgId)
.setNegativeButton(R.string.dialog_report, reportButtonListener)
.setPositiveButton(R.string.dialog_dismiss, buttonListener)
.setIcon(out.resourceId)
public void launchGoogleHelp() {
}
- public void sendGoogleFeedback() {}
+ public void sendGoogleFeedback(int category) {}
}