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.Context; 37 import android.content.res.AssetFileDescriptor; 38 import android.database.Cursor; 39 import android.database.sqlite.SQLiteException; 40 import android.net.Uri; 41 import android.provider.OpenableColumns; 42 import android.util.EventLog; 43 import android.util.Log; 44 45 import com.android.bluetooth.R; 46 47 import java.io.File; 48 import java.io.FileInputStream; 49 import java.io.FileNotFoundException; 50 import java.io.IOException; 51 52 /** 53 * This class stores information about a single sending file It will only be 54 * used for outbound share. 55 */ 56 public class BluetoothOppSendFileInfo { 57 private static final String TAG = "BluetoothOppSendFileInfo"; 58 59 private static final boolean D = Constants.DEBUG; 60 61 62 /** Reusable SendFileInfo for error status. */ 63 static final BluetoothOppSendFileInfo SEND_FILE_INFO_ERROR = 64 new BluetoothOppSendFileInfo(null, null, 0, null, BluetoothShare.STATUS_FILE_ERROR); 65 66 /** readable media file name */ 67 public final String mFileName; 68 69 /** media file input stream */ 70 public final FileInputStream mInputStream; 71 72 /** vCard string data */ 73 public final String mData; 74 75 public final int mStatus; 76 77 public final String mMimetype; 78 79 public final long mLength; 80 81 /** for media file */ BluetoothOppSendFileInfo(String fileName, String type, long length, FileInputStream inputStream, int status)82 public BluetoothOppSendFileInfo(String fileName, String type, long length, 83 FileInputStream inputStream, int status) { 84 mFileName = fileName; 85 mMimetype = type; 86 mLength = length; 87 mInputStream = inputStream; 88 mStatus = status; 89 mData = null; 90 } 91 92 /** for vCard, or later for vCal, vNote. Not used currently */ BluetoothOppSendFileInfo(String data, String type, long length, int status)93 public BluetoothOppSendFileInfo(String data, String type, long length, int status) { 94 mFileName = null; 95 mInputStream = null; 96 mData = data; 97 mMimetype = type; 98 mLength = length; 99 mStatus = status; 100 } 101 generateFileInfo(Context context, Uri uri, String type, boolean fromExternal)102 public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri, String type, 103 boolean fromExternal) { 104 ContentResolver contentResolver = context.getContentResolver(); 105 String scheme = uri.getScheme(); 106 String fileName = null; 107 String contentType; 108 long length = 0; 109 // Support all Uri with "content" scheme 110 // This will allow more 3rd party applications to share files via 111 // bluetooth 112 if ("content".equals(scheme)) { 113 contentType = contentResolver.getType(uri); 114 Cursor metadataCursor; 115 try { 116 metadataCursor = contentResolver.query(uri, new String[]{ 117 OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE 118 }, null, null, null); 119 } catch (SQLiteException e) { 120 // some content providers don't support the DISPLAY_NAME or SIZE columns 121 metadataCursor = null; 122 } catch (SecurityException e) { 123 Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri); 124 return SEND_FILE_INFO_ERROR; 125 } 126 127 if (metadataCursor != null) { 128 try { 129 if (metadataCursor.moveToFirst()) { 130 int indexName = metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 131 int indexSize = metadataCursor.getColumnIndex(OpenableColumns.SIZE); 132 if (indexName != -1) { 133 fileName = metadataCursor.getString(indexName); 134 } 135 if (indexSize != -1) { 136 length = metadataCursor.getLong(indexSize); 137 } 138 if (D) { 139 Log.d(TAG, "fileName = " + fileName + " length = " + length); 140 } 141 } 142 } finally { 143 metadataCursor.close(); 144 } 145 } 146 if (fileName == null) { 147 // use last segment of URI if DISPLAY_NAME query fails 148 fileName = uri.getLastPathSegment(); 149 if (D) Log.d(TAG, "fileName from URI :" + fileName); 150 } 151 } else if ("file".equals(scheme)) { 152 if (uri.getPath() == null) { 153 Log.e(TAG, "Invalid URI path: " + uri); 154 return SEND_FILE_INFO_ERROR; 155 } 156 if (fromExternal && !BluetoothOppUtility.isInExternalStorageDir(uri)) { 157 EventLog.writeEvent(0x534e4554, "35310991", -1, uri.getPath()); 158 Log.e(TAG, "File based URI not in Environment.getExternalStorageDirectory() is not " 159 + "allowed."); 160 return SEND_FILE_INFO_ERROR; 161 } 162 fileName = uri.getLastPathSegment(); 163 contentType = type; 164 File f = new File(uri.getPath()); 165 length = f.length(); 166 } else { 167 // currently don't accept other scheme 168 return SEND_FILE_INFO_ERROR; 169 } 170 FileInputStream is = null; 171 if (scheme.equals("content")) { 172 try { 173 // We've found that content providers don't always have the 174 // right size in _OpenableColumns.SIZE 175 // As a second source of getting the correct file length, 176 // get a file descriptor and get the stat length 177 AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r"); 178 long statLength = fd.getLength(); 179 if (length != statLength && statLength > 0) { 180 Log.e(TAG, "Content provider length is wrong (" + Long.toString(length) 181 + "), using stat length (" + Long.toString(statLength) + ")"); 182 length = statLength; 183 } 184 185 try { 186 // This creates an auto-closing input-stream, so 187 // the file descriptor will be closed whenever the InputStream 188 // is closed. 189 is = fd.createInputStream(); 190 191 // If the database doesn't contain the file size, get the size 192 // by reading through the entire stream 193 if (length == 0) { 194 length = getStreamSize(is); 195 Log.w(TAG, "File length not provided. Length from stream = " + length); 196 // Reset the stream 197 fd = contentResolver.openAssetFileDescriptor(uri, "r"); 198 is = fd.createInputStream(); 199 } 200 } catch (IOException e) { 201 try { 202 fd.close(); 203 } catch (IOException e2) { 204 // Ignore 205 } 206 } 207 } catch (FileNotFoundException e) { 208 // Ignore 209 } catch (SecurityException e) { 210 return SEND_FILE_INFO_ERROR; 211 } 212 } 213 214 if (is == null) { 215 try { 216 is = (FileInputStream) contentResolver.openInputStream(uri); 217 218 // If the database doesn't contain the file size, get the size 219 // by reading through the entire stream 220 if (length == 0) { 221 length = getStreamSize(is); 222 // Reset the stream 223 is = (FileInputStream) contentResolver.openInputStream(uri); 224 } 225 } catch (FileNotFoundException e) { 226 return SEND_FILE_INFO_ERROR; 227 } catch (IOException e) { 228 return SEND_FILE_INFO_ERROR; 229 } 230 } 231 232 if (length == 0) { 233 Log.e(TAG, "Could not determine size of file"); 234 return SEND_FILE_INFO_ERROR; 235 } else if (length > 0xffffffffL) { 236 Log.e(TAG, "File of size: " + length + " bytes can't be transferred"); 237 throw new IllegalArgumentException(context 238 .getString(R.string.bluetooth_opp_file_limit_exceeded)); 239 } 240 241 return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0); 242 } 243 getStreamSize(FileInputStream is)244 private static long getStreamSize(FileInputStream is) throws IOException { 245 long length = 0; 246 byte[] unused = new byte[4096]; 247 int bytesRead = is.read(unused, 0, 4096); 248 while (bytesRead != -1) { 249 length += bytesRead; 250 bytesRead = is.read(unused, 0, 4096); 251 } 252 return length; 253 } 254 } 255