1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.devcamera; 17 18 import android.content.ContentResolver; 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.os.SystemClock; 23 import android.provider.MediaStore; 24 import android.util.Log; 25 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.nio.ByteBuffer; 31 import java.nio.channels.FileChannel; 32 33 /** 34 * This class has methods required to save a JPEG to disk as well as update the 35 * MediaStore database. 36 */ 37 38 39 public class MediaSaver { 40 private static final String TAG = "Snappy_MediaSaver"; 41 private static final String MY_PREFS_NAME = "SnappyPrefs"; 42 43 // MediaStore is slow/broken 44 private static final boolean UDPATE_MEDIA_STORE = true; 45 46 getNextInt(Context context, String id)47 public static int getNextInt(Context context, String id) { 48 SharedPreferences prefs = context.getSharedPreferences(MY_PREFS_NAME, Context.MODE_PRIVATE); 49 int i = prefs.getInt(id, 1); 50 SharedPreferences.Editor editor = prefs.edit(); 51 editor.putInt(id, i+1); 52 editor.commit(); 53 return i; 54 } 55 56 /** 57 * @param context Application context. 58 * @param depthCloudData Depth cloud byte buffer. 59 */ saveDepth(Context context, ByteBuffer depthCloudData)60 public static String saveDepth(Context context, ByteBuffer depthCloudData) { 61 String filename = ""; 62 try { 63 File file; 64 int i = getNextInt(context, "depthCounter"); 65 filename = String.format("/sdcard/DCIM/Depth_%05d.img", i); 66 file = new File(filename); 67 if (!file.createNewFile()) { 68 throw new IOException(filename); 69 } 70 71 long t0 = SystemClock.uptimeMillis(); 72 FileOutputStream fos = new FileOutputStream(file); 73 FileChannel channel = fos.getChannel(); 74 int bytesWritten = 0; 75 int byteCount = 0; 76 while (depthCloudData.hasRemaining()) { 77 byteCount = channel.write(depthCloudData); 78 if (0 == byteCount) { 79 throw new IOException(filename); 80 } else { 81 bytesWritten += byteCount; 82 } 83 } 84 channel.close(); 85 fos.flush(); 86 fos.close(); 87 long t1 = SystemClock.uptimeMillis(); 88 89 Log.v(TAG, String.format("Wrote Depth %d bytes as %s in %.3f seconds", 90 bytesWritten, file, (t1 - t0) * 0.001)); 91 } catch (IOException e) { 92 Log.e(TAG, "Error creating new file: ", e); 93 } 94 return filename; 95 } 96 97 /** 98 * @param context Application context. 99 * @param jpegData JPEG byte stream. 100 */ saveJpeg(Context context, byte[] jpegData, ContentResolver resolver)101 public static String saveJpeg(Context context, byte[] jpegData, ContentResolver resolver) { 102 String filename = ""; 103 try { 104 File file; 105 while (true) { 106 int i = getNextInt(context, "counter"); 107 filename = String.format("/sdcard/DCIM/Camera/SNAP_%05d.JPG", i); 108 file = new File(filename); 109 if (file.createNewFile()) { 110 break; 111 } 112 } 113 114 long t0 = SystemClock.uptimeMillis(); 115 OutputStream os = new FileOutputStream(file); 116 os.write(jpegData); 117 os.flush(); 118 os.close(); 119 long t1 = SystemClock.uptimeMillis(); 120 121 // update MediaStore so photos apps can find photos right away. 122 if (UDPATE_MEDIA_STORE) { 123 // really slow for some reason: MediaStore.Images.Media.insertImage(resolver, file.getAbsolutePath(), file.getName(), file.getName()); 124 insertImage(resolver, file); 125 } 126 long t2 = SystemClock.uptimeMillis(); 127 128 Log.v(TAG, String.format("Wrote JPEG %d bytes as %s in %.3f seconds; mediastore update = %.3f secs", 129 jpegData.length, file, (t1 - t0) * 0.001, (t2 - t1) * 0.001) ); 130 } catch (IOException e) { 131 Log.e(TAG, "Error creating new file: ", e); 132 } 133 return filename; 134 } 135 136 137 // We use this instead of MediaStore.Images.Media.insertImage() because we want to add date metadata insertImage(ContentResolver cr, File file)138 public static void insertImage(ContentResolver cr, File file) { 139 140 ContentValues values = new ContentValues(); 141 values.put(MediaStore.Images.Media.TITLE, file.getName()); 142 values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName()); 143 values.put(MediaStore.Images.Media.DESCRIPTION, file.getName()); 144 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); 145 values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath()); 146 // Add the date meta data to ensure the image is added at the front of the gallery 147 values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis()); 148 values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()); 149 150 try { 151 cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 152 } catch (Exception e) { 153 Log.w(TAG, "Error updating media store for " + file, e); 154 } 155 } 156 157 } 158