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 33 package com.android.bluetooth.opp; 34 35 import android.content.ContentResolver; 36 import android.content.ContentValues; 37 import android.content.Context; 38 import android.database.Cursor; 39 import android.net.Uri; 40 import android.os.Environment; 41 import android.provider.MediaStore; 42 import android.util.Log; 43 44 import java.io.UnsupportedEncodingException; 45 import java.text.DateFormat; 46 import java.text.SimpleDateFormat; 47 import java.util.Calendar; 48 49 /** 50 * This class stores information about a single receiving file. It will only be 51 * used for inbounds share, e.g. receive a file to determine a correct save file 52 * name 53 */ 54 public class BluetoothOppReceiveFileInfo { 55 private static final boolean D = Constants.DEBUG; 56 private static final boolean V = Constants.VERBOSE; 57 private static String sDesiredStoragePath = null; 58 59 /* To truncate the name of the received file if the length exceeds 237 */ 60 private static final int OPP_LENGTH_OF_FILE_NAME = 237; 61 62 63 /** absolute store file name */ 64 public String mFileName; 65 66 public long mLength; 67 68 public int mStatus; 69 70 public String mData; 71 72 public Uri mInsertUri; 73 BluetoothOppReceiveFileInfo(String data, long length, int status)74 public BluetoothOppReceiveFileInfo(String data, long length, int status) { 75 mData = data; 76 mStatus = status; 77 mLength = length; 78 } 79 BluetoothOppReceiveFileInfo(String filename, long length, Uri insertUri, int status)80 public BluetoothOppReceiveFileInfo(String filename, long length, Uri insertUri, int status) { 81 mFileName = filename; 82 mStatus = status; 83 mInsertUri = insertUri; 84 mLength = length; 85 } 86 BluetoothOppReceiveFileInfo(int status)87 public BluetoothOppReceiveFileInfo(int status) { 88 this(null, 0, null, status); 89 } 90 91 // public static final int BATCH_STATUS_CANCELED = 4; generateFileInfo(Context context, int id)92 public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) { 93 94 ContentResolver contentResolver = context.getContentResolver(); 95 Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); 96 String filename = null, hint = null, mimeType = null; 97 long length = 0; 98 Cursor metadataCursor = contentResolver.query(contentUri, new String[]{ 99 BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE 100 }, null, null, null); 101 if (metadataCursor != null) { 102 try { 103 if (metadataCursor.moveToFirst()) { 104 hint = metadataCursor.getString(0); 105 length = metadataCursor.getLong(1); 106 mimeType = metadataCursor.getString(2); 107 } 108 } finally { 109 metadataCursor.close(); 110 } 111 } 112 113 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 114 if (D) { 115 Log.d(Constants.TAG, "Receive File aborted - no external storage"); 116 } 117 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD); 118 } 119 120 filename = choosefilename(hint); 121 if (filename == null) { 122 // should not happen. It must be pre-rejected 123 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 124 } 125 String extension = null; 126 int dotIndex = filename.lastIndexOf("."); 127 if (dotIndex < 0) { 128 if (mimeType == null) { 129 // should not happen. It must be pre-rejected 130 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 131 } else { 132 extension = ""; 133 } 134 } else { 135 extension = filename.substring(dotIndex); 136 filename = filename.substring(0, dotIndex); 137 } 138 if (D) { 139 Log.d(Constants.TAG, " File Name " + filename); 140 } 141 142 if (filename.getBytes().length > OPP_LENGTH_OF_FILE_NAME) { 143 /* Including extn of the file, Linux supports 255 character as a maximum length of the 144 * file name to be created. Hence, Instead of sending OBEX_HTTP_INTERNAL_ERROR, 145 * as a response, truncate the length of the file name and save it. This check majorly 146 * helps in the case of vcard, where Phone book app supports contact name to be saved 147 * more than 255 characters, But the server rejects the card just because the length of 148 * vcf file name received exceeds 255 Characters. 149 */ 150 Log.i(Constants.TAG, " File Name Length :" + filename.length()); 151 Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length); 152 153 try { 154 byte[] oldfilename = filename.getBytes("UTF-8"); 155 byte[] newfilename = new byte[OPP_LENGTH_OF_FILE_NAME]; 156 System.arraycopy(oldfilename, 0, newfilename, 0, OPP_LENGTH_OF_FILE_NAME); 157 filename = new String(newfilename, "UTF-8"); 158 } catch (UnsupportedEncodingException e) { 159 Log.e(Constants.TAG, "Exception: " + e); 160 } 161 if (D) { 162 Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename); 163 } 164 } 165 166 DateFormat dateFormat = new SimpleDateFormat("_hhmmss"); 167 String currentTime = dateFormat.format(Calendar.getInstance().getTime()); 168 String fullfilename = filename + currentTime + extension; 169 170 if (V) { 171 Log.v(Constants.TAG, "Generated received filename " + fullfilename); 172 } 173 174 Uri insertUri = null; 175 ContentValues mediaContentValues = new ContentValues(); 176 mediaContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fullfilename); 177 mediaContentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); 178 mediaContentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, 179 Environment.DIRECTORY_DOWNLOADS); 180 insertUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, 181 mediaContentValues); 182 183 if (insertUri == null) { 184 if (D) { 185 Log.e(Constants.TAG, "Error when creating file " + fullfilename); 186 } 187 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); 188 } 189 190 Log.d(Constants.TAG, "file crated, insertUri:" + insertUri.toString()); 191 192 return new BluetoothOppReceiveFileInfo(fullfilename, length, insertUri, 0); 193 } 194 choosefilename(String hint)195 private static String choosefilename(String hint) { 196 String filename = null; 197 198 // First, try to use the hint from the application, if there's one 199 if (filename == null && !(hint == null) && !hint.endsWith("/") && !hint.endsWith("\\")) { 200 // Prevent abuse of path backslashes by converting all backlashes '\\' chars 201 // to UNIX-style forward-slashes '/' 202 hint = hint.replace('\\', '/'); 203 // Convert all whitespace characters to spaces. 204 hint = hint.replaceAll("\\s", " "); 205 // Replace illegal fat filesystem characters from the 206 // filename hint i.e. :"<>*?| with something safe. 207 hint = hint.replaceAll("[:\"<>*?|]", "_"); 208 if (V) { 209 Log.v(Constants.TAG, "getting filename from hint"); 210 } 211 int index = hint.lastIndexOf('/') + 1; 212 if (index > 0) { 213 filename = hint.substring(index); 214 } else { 215 filename = hint; 216 } 217 } 218 return filename; 219 } 220 } 221