BluetoothOppReceiveFileInfo.java revision 38416553a26e55963e11276f1029d78e9e6cad70
1/* 2 * Copyright (c) 2008-2009, Motorola, Inc. 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * - Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * 12 * - Redistributions in binary form must reproduce the above copyright notice, 13 * this list of conditions and the following disclaimer in the documentation 14 * and/or other materials provided with the distribution. 15 * 16 * - Neither the name of the Motorola, Inc. nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33package com.android.bluetooth.opp; 34 35import java.io.File; 36import java.io.FileNotFoundException; 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.util.Random; 40 41import android.content.ContentResolver; 42import android.content.ContentValues; 43import android.content.Context; 44import android.database.Cursor; 45import android.net.Uri; 46import android.os.Environment; 47import android.os.StatFs; 48import android.os.SystemClock; 49import android.util.Log; 50 51/** 52 * This class stores information about a single receiving file. It will only be 53 * used for inbounds share, e.g. receive a file to determine a correct save file 54 * name 55 */ 56public class BluetoothOppReceiveFileInfo { 57 private static final boolean D = Constants.DEBUG; 58 private static final boolean V = Constants.VERBOSE; 59 60 /** absolute store file name */ 61 public String mFileName; 62 63 public long mLength; 64 65 public FileOutputStream mOutputStream; 66 67 public int mStatus; 68 69 public String mData; 70 71 public BluetoothOppReceiveFileInfo(String data, long length, int status) { 72 mData = data; 73 mStatus = status; 74 mLength = length; 75 } 76 77 public BluetoothOppReceiveFileInfo(String filename, long length, FileOutputStream outputStream, 78 int status) { 79 mFileName = filename; 80 mOutputStream = outputStream; 81 mStatus = status; 82 mLength = length; 83 } 84 85 public BluetoothOppReceiveFileInfo(int status) { 86 this(null, 0, null, status); 87 } 88 89 // public static final int BATCH_STATUS_CANCELED = 4; 90 public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) { 91 92 ContentResolver contentResolver = context.getContentResolver(); 93 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 94 String filename = null, hint = null; 95 long length = 0; 96 Cursor metadataCursor = contentResolver.query(contentUri, new String[] { 97 BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE 98 }, null, null, null); 99 if (metadataCursor != null) { 100 try { 101 if (metadataCursor.moveToFirst()) { 102 hint = metadataCursor.getString(0); 103 length = metadataCursor.getInt(1); 104 } 105 } finally { 106 metadataCursor.close(); 107 } 108 } 109 110 File base = null; 111 StatFs stat = null; 112 113 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 114 String root = Environment.getExternalStorageDirectory().getPath(); 115 base = new File(root + Constants.DEFAULT_STORE_SUBDIR); 116 if (!base.isDirectory() && !base.mkdir()) { 117 if (D) Log.d(Constants.TAG, "Receive File aborted - can't create base directory " 118 + base.getPath()); 119 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 120 } 121 stat = new StatFs(base.getPath()); 122 } else { 123 if (D) Log.d(Constants.TAG, "Receive File aborted - no external storage"); 124 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD); 125 } 126 127 /* 128 * Check whether there's enough space on the target filesystem to save 129 * the file. Put a bit of margin (in case creating the file grows the 130 * system by a few blocks). 131 */ 132 if (stat.getBlockSize() * ((long)stat.getAvailableBlocks() - 4) < length) { 133 if (D) Log.d(Constants.TAG, "Receive File aborted - not enough free space"); 134 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_SDCARD_FULL); 135 } 136 137 filename = choosefilename(hint); 138 if (filename == null) { 139 // should not happen. It must be pre-rejected 140 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 141 } 142 String extension = null; 143 int dotIndex = filename.indexOf('.'); 144 if (dotIndex < 0) { 145 // should not happen. It must be pre-rejected 146 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 147 } else { 148 extension = filename.substring(dotIndex); 149 filename = filename.substring(0, dotIndex); 150 } 151 filename = base.getPath() + File.separator + filename; 152 // Generate a unique filename, create the file, return it. 153 String fullfilename = chooseUniquefilename(filename, extension); 154 if (V) Log.v(Constants.TAG, "Generated received filename " + fullfilename); 155 156 if (fullfilename != null) { 157 try { 158 new FileOutputStream(fullfilename).close(); 159 int index = fullfilename.lastIndexOf('/') + 1; 160 // update display name 161 if (index > 0) { 162 String displayName = fullfilename.substring(index); 163 if (V) Log.v(Constants.TAG, "New display name " + displayName); 164 ContentValues updateValues = new ContentValues(); 165 updateValues.put(BluetoothShare.FILENAME_HINT, displayName); 166 context.getContentResolver().update(contentUri, updateValues, null, null); 167 168 } 169 return new BluetoothOppReceiveFileInfo(fullfilename, length, new FileOutputStream( 170 fullfilename), 0); 171 } catch (IOException e) { 172 if (D) Log.e(Constants.TAG, "Error when creating file " + fullfilename); 173 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 174 } 175 } else { 176 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 177 } 178 179 } 180 181 private static String chooseUniquefilename(String filename, String extension) { 182 String fullfilename = filename + extension; 183 if (!new File(fullfilename).exists()) { 184 return fullfilename; 185 } 186 filename = filename + Constants.filename_SEQUENCE_SEPARATOR; 187 /* 188 * This number is used to generate partially randomized filenames to 189 * avoid collisions. It starts at 1. The next 9 iterations increment it 190 * by 1 at a time (up to 10). The next 9 iterations increment it by 1 to 191 * 10 (random) at a time. The next 9 iterations increment it by 1 to 100 192 * (random) at a time. ... Up to the point where it increases by 193 * 100000000 at a time. (the maximum value that can be reached is 194 * 1000000000) As soon as a number is reached that generates a filename 195 * that doesn't exist, that filename is used. If the filename coming in 196 * is [base].[ext], the generated filenames are [base]-[sequence].[ext]. 197 */ 198 Random rnd = new Random(SystemClock.uptimeMillis()); 199 int sequence = 1; 200 for (int magnitude = 1; magnitude < 1000000000; magnitude *= 10) { 201 for (int iteration = 0; iteration < 9; ++iteration) { 202 fullfilename = filename + sequence + extension; 203 if (!new File(fullfilename).exists()) { 204 return fullfilename; 205 } 206 if (V) Log.v(Constants.TAG, "file with sequence number " + sequence + " exists"); 207 sequence += rnd.nextInt(magnitude) + 1; 208 } 209 } 210 return null; 211 } 212 213 private static String choosefilename(String hint) { 214 String filename = null; 215 216 // First, try to use the hint from the application, if there's one 217 if (filename == null && !(hint == null) && !hint.endsWith("/") && !hint.endsWith("\\")) { 218 // Prevent abuse of path backslashes by converting all backlashes '\\' chars 219 // to UNIX-style forward-slashes '/' 220 hint = hint.replace('\\', '/'); 221 if (V) Log.v(Constants.TAG, "getting filename from hint"); 222 int index = hint.lastIndexOf('/') + 1; 223 if (index > 0) { 224 filename = hint.substring(index); 225 } else { 226 filename = hint; 227 } 228 } 229 return filename; 230 } 231} 232