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.Context; 39 import android.content.res.AssetFileDescriptor; 40 import android.database.Cursor; 41 import android.database.sqlite.SQLiteException; 42 import android.net.Uri; 43 import android.provider.OpenableColumns; 44 import android.util.EventLog; 45 import android.util.Log; 46 47 import com.android.bluetooth.BluetoothMethodProxy; 48 import com.android.bluetooth.BluetoothStatsLog; 49 import com.android.bluetooth.R; 50 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 51 52 import java.io.File; 53 import java.io.FileInputStream; 54 import java.io.FileNotFoundException; 55 import java.io.IOException; 56 57 /** 58 * This class stores information about a single sending file It will only be used for outbound 59 * share. 60 */ 61 // Next tag value for ContentProfileErrorReportUtils.report(): 15 62 public class BluetoothOppSendFileInfo { 63 private static final String TAG = "BluetoothOppSendFileInfo"; 64 65 /** Reusable SendFileInfo for error status. */ 66 static final BluetoothOppSendFileInfo SEND_FILE_INFO_ERROR = 67 new BluetoothOppSendFileInfo(null, null, 0, null, BluetoothShare.STATUS_FILE_ERROR); 68 69 /** readable media file name */ 70 public final String mFileName; 71 72 /** media file input stream */ 73 public final FileInputStream mInputStream; 74 75 /** vCard string data */ 76 public final String mData; 77 78 public final int mStatus; 79 80 public final String mMimetype; 81 82 public final long mLength; 83 84 /** for media file */ BluetoothOppSendFileInfo( String fileName, String type, long length, FileInputStream inputStream, int status)85 public BluetoothOppSendFileInfo( 86 String fileName, String type, long length, FileInputStream inputStream, int status) { 87 mFileName = fileName; 88 mMimetype = type; 89 mLength = length; 90 mInputStream = inputStream; 91 mStatus = status; 92 mData = null; 93 } 94 95 /** for vCard, or later for vCal, vNote. Not used currently */ BluetoothOppSendFileInfo(String data, String type, long length, int status)96 public BluetoothOppSendFileInfo(String data, String type, long length, int status) { 97 mFileName = null; 98 mInputStream = null; 99 mData = data; 100 mMimetype = type; 101 mLength = length; 102 mStatus = status; 103 } 104 generateFileInfo( Context context, Uri uri, String type, boolean fromExternal)105 public static BluetoothOppSendFileInfo generateFileInfo( 106 Context context, Uri uri, String type, boolean fromExternal) { 107 ContentResolver contentResolver = context.getContentResolver(); 108 String scheme = uri.getScheme(); 109 String fileName = null; 110 String contentType; 111 long length = 0; 112 // Support all Uri with "content" scheme 113 // This will allow more 3rd party applications to share files via 114 // bluetooth 115 if ("content".equals(scheme)) { 116 if (fromExternal && BluetoothOppUtility.isForbiddenContent(uri)) { 117 EventLog.writeEvent(0x534e4554, "179910660", -1, uri.toString()); 118 Log.e(TAG, "Content from forbidden URI is not allowed."); 119 ContentProfileErrorReportUtils.report( 120 BluetoothProfile.OPP, 121 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 122 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 123 0); 124 return SEND_FILE_INFO_ERROR; 125 } 126 127 contentType = contentResolver.getType(uri); 128 Cursor metadataCursor; 129 try { 130 metadataCursor = 131 BluetoothMethodProxy.getInstance() 132 .contentResolverQuery( 133 contentResolver, 134 uri, 135 new String[] { 136 OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE 137 }, 138 null, 139 null, 140 null); 141 } catch (SQLiteException e) { 142 ContentProfileErrorReportUtils.report( 143 BluetoothProfile.OPP, 144 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 145 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 146 1); 147 // some content providers don't support the DISPLAY_NAME or SIZE columns 148 metadataCursor = null; 149 } catch (SecurityException e) { 150 ContentProfileErrorReportUtils.report( 151 BluetoothProfile.OPP, 152 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 153 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 154 2); 155 Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri); 156 return SEND_FILE_INFO_ERROR; 157 } 158 159 if (metadataCursor != null) { 160 try { 161 if (metadataCursor.moveToFirst()) { 162 int indexName = metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 163 int indexSize = metadataCursor.getColumnIndex(OpenableColumns.SIZE); 164 if (indexName != -1) { 165 fileName = metadataCursor.getString(indexName); 166 } 167 if (indexSize != -1) { 168 length = metadataCursor.getLong(indexSize); 169 } 170 Log.d(TAG, "fileName = " + fileName + " length = " + length); 171 } 172 } finally { 173 metadataCursor.close(); 174 } 175 } 176 if (fileName == null) { 177 // use last segment of URI if DISPLAY_NAME query fails 178 fileName = uri.getLastPathSegment(); 179 Log.d(TAG, "fileName from URI :" + fileName); 180 } 181 } else if ("file".equals(scheme)) { 182 if (uri.getPath() == null) { 183 Log.e(TAG, "Invalid URI path: " + uri); 184 ContentProfileErrorReportUtils.report( 185 BluetoothProfile.OPP, 186 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 187 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 188 3); 189 return SEND_FILE_INFO_ERROR; 190 } 191 if (fromExternal && !BluetoothOppUtility.isInExternalStorageDir(uri)) { 192 EventLog.writeEvent(0x534e4554, "35310991", -1, uri.getPath()); 193 Log.e( 194 TAG, 195 "File based URI not in Environment.getExternalStorageDirectory() is not " 196 + "allowed."); 197 ContentProfileErrorReportUtils.report( 198 BluetoothProfile.OPP, 199 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 200 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 201 4); 202 return SEND_FILE_INFO_ERROR; 203 } 204 fileName = uri.getLastPathSegment(); 205 contentType = type; 206 File f = new File(uri.getPath()); 207 length = f.length(); 208 } else { 209 // currently don't accept other scheme 210 return SEND_FILE_INFO_ERROR; 211 } 212 FileInputStream is = null; 213 if (scheme.equals("content")) { 214 try { 215 // We've found that content providers don't always have the 216 // right size in _OpenableColumns.SIZE 217 // As a second source of getting the correct file length, 218 // get a file descriptor and get the stat length 219 AssetFileDescriptor fd = 220 BluetoothMethodProxy.getInstance() 221 .contentResolverOpenAssetFileDescriptor(contentResolver, uri, "r"); 222 long statLength = fd.getLength(); 223 if (length != statLength && statLength > 0) { 224 Log.e( 225 TAG, 226 "Content provider length is wrong (" 227 + Long.toString(length) 228 + "), using stat length (" 229 + Long.toString(statLength) 230 + ")"); 231 ContentProfileErrorReportUtils.report( 232 BluetoothProfile.OPP, 233 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 234 BluetoothStatsLog 235 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 236 5); 237 length = statLength; 238 } 239 240 try { 241 // This creates an auto-closing input-stream, so 242 // the file descriptor will be closed whenever the InputStream 243 // is closed. 244 is = fd.createInputStream(); 245 246 // If the database doesn't contain the file size, get the size 247 // by reading through the entire stream 248 if (length == 0) { 249 length = getStreamSize(is); 250 Log.w(TAG, "File length not provided. Length from stream = " + length); 251 ContentProfileErrorReportUtils.report( 252 BluetoothProfile.OPP, 253 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 254 BluetoothStatsLog 255 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_WARN, 256 6); 257 // Reset the stream 258 fd = 259 BluetoothMethodProxy.getInstance() 260 .contentResolverOpenAssetFileDescriptor( 261 contentResolver, uri, "r"); 262 is = fd.createInputStream(); 263 } 264 } catch (IOException e) { 265 ContentProfileErrorReportUtils.report( 266 BluetoothProfile.OPP, 267 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 268 BluetoothStatsLog 269 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 270 7); 271 try { 272 fd.close(); 273 } catch (IOException e2) { 274 ContentProfileErrorReportUtils.report( 275 BluetoothProfile.OPP, 276 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 277 BluetoothStatsLog 278 .BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 279 8); 280 // Ignore 281 } 282 } 283 } catch (FileNotFoundException e) { 284 ContentProfileErrorReportUtils.report( 285 BluetoothProfile.OPP, 286 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 287 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 288 9); 289 // Ignore 290 } catch (SecurityException e) { 291 ContentProfileErrorReportUtils.report( 292 BluetoothProfile.OPP, 293 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 294 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 295 10); 296 return SEND_FILE_INFO_ERROR; 297 } 298 } 299 300 if (is == null) { 301 try { 302 is = 303 (FileInputStream) 304 BluetoothMethodProxy.getInstance() 305 .contentResolverOpenInputStream(contentResolver, uri); 306 307 // If the database doesn't contain the file size, get the size 308 // by reading through the entire stream 309 if (length == 0) { 310 length = getStreamSize(is); 311 // Reset the stream 312 is = 313 (FileInputStream) 314 BluetoothMethodProxy.getInstance() 315 .contentResolverOpenInputStream(contentResolver, uri); 316 } 317 } catch (FileNotFoundException e) { 318 ContentProfileErrorReportUtils.report( 319 BluetoothProfile.OPP, 320 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 321 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 322 11); 323 return SEND_FILE_INFO_ERROR; 324 } catch (IOException e) { 325 ContentProfileErrorReportUtils.report( 326 BluetoothProfile.OPP, 327 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 328 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 329 12); 330 return SEND_FILE_INFO_ERROR; 331 } 332 } 333 334 if (length == 0) { 335 Log.e(TAG, "Could not determine size of file"); 336 ContentProfileErrorReportUtils.report( 337 BluetoothProfile.OPP, 338 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 339 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 340 13); 341 return SEND_FILE_INFO_ERROR; 342 } else if (length > 0xffffffffL) { 343 Log.e(TAG, "File of size: " + length + " bytes can't be transferred"); 344 ContentProfileErrorReportUtils.report( 345 BluetoothProfile.OPP, 346 BluetoothProtoEnums.BLUETOOTH_OPP_SEND_FILE_INFO, 347 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 348 14); 349 throw new IllegalArgumentException( 350 context.getString(R.string.bluetooth_opp_file_limit_exceeded)); 351 } 352 353 return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0); 354 } 355 getStreamSize(FileInputStream is)356 private static long getStreamSize(FileInputStream is) throws IOException { 357 long length = 0; 358 byte[] unused = new byte[4096]; 359 int bytesRead = is.read(unused, 0, 4096); 360 while (bytesRead != -1) { 361 length += bytesRead; 362 bytesRead = is.read(unused, 0, 4096); 363 } 364 return length; 365 } 366 } 367