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