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 if (fromExternal && BluetoothOppUtility.isForbiddenContent(uri)) { 114 EventLog.writeEvent(0x534e4554, "179910660", -1, uri.toString()); 115 Log.e(TAG, "Content from forbidden URI is not allowed."); 116 return SEND_FILE_INFO_ERROR; 117 } 118 119 contentType = contentResolver.getType(uri); 120 Cursor metadataCursor; 121 try { 122 metadataCursor = contentResolver.query(uri, new String[]{ 123 OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE 124 }, null, null, null); 125 } catch (SQLiteException e) { 126 // some content providers don't support the DISPLAY_NAME or SIZE columns 127 metadataCursor = null; 128 } catch (SecurityException e) { 129 Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri); 130 return SEND_FILE_INFO_ERROR; 131 } 132 133 if (metadataCursor != null) { 134 try { 135 if (metadataCursor.moveToFirst()) { 136 int indexName = metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); 137 int indexSize = metadataCursor.getColumnIndex(OpenableColumns.SIZE); 138 if (indexName != -1) { 139 fileName = metadataCursor.getString(indexName); 140 } 141 if (indexSize != -1) { 142 length = metadataCursor.getLong(indexSize); 143 } 144 if (D) { 145 Log.d(TAG, "fileName = " + fileName + " length = " + length); 146 } 147 } 148 } finally { 149 metadataCursor.close(); 150 } 151 } 152 if (fileName == null) { 153 // use last segment of URI if DISPLAY_NAME query fails 154 fileName = uri.getLastPathSegment(); 155 if (D) Log.d(TAG, "fileName from URI :" + fileName); 156 } 157 } else if ("file".equals(scheme)) { 158 if (uri.getPath() == null) { 159 Log.e(TAG, "Invalid URI path: " + uri); 160 return SEND_FILE_INFO_ERROR; 161 } 162 if (fromExternal && !BluetoothOppUtility.isInExternalStorageDir(uri)) { 163 EventLog.writeEvent(0x534e4554, "35310991", -1, uri.getPath()); 164 Log.e(TAG, "File based URI not in Environment.getExternalStorageDirectory() is not " 165 + "allowed."); 166 return SEND_FILE_INFO_ERROR; 167 } 168 fileName = uri.getLastPathSegment(); 169 contentType = type; 170 File f = new File(uri.getPath()); 171 length = f.length(); 172 } else { 173 // currently don't accept other scheme 174 return SEND_FILE_INFO_ERROR; 175 } 176 FileInputStream is = null; 177 if (scheme.equals("content")) { 178 try { 179 // We've found that content providers don't always have the 180 // right size in _OpenableColumns.SIZE 181 // As a second source of getting the correct file length, 182 // get a file descriptor and get the stat length 183 AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r"); 184 long statLength = fd.getLength(); 185 if (length != statLength && statLength > 0) { 186 Log.e(TAG, "Content provider length is wrong (" + Long.toString(length) 187 + "), using stat length (" + Long.toString(statLength) + ")"); 188 length = statLength; 189 } 190 191 try { 192 // This creates an auto-closing input-stream, so 193 // the file descriptor will be closed whenever the InputStream 194 // is closed. 195 is = fd.createInputStream(); 196 197 // If the database doesn't contain the file size, get the size 198 // by reading through the entire stream 199 if (length == 0) { 200 length = getStreamSize(is); 201 Log.w(TAG, "File length not provided. Length from stream = " + length); 202 // Reset the stream 203 fd = contentResolver.openAssetFileDescriptor(uri, "r"); 204 is = fd.createInputStream(); 205 } 206 } catch (IOException e) { 207 try { 208 fd.close(); 209 } catch (IOException e2) { 210 // Ignore 211 } 212 } 213 } catch (FileNotFoundException e) { 214 // Ignore 215 } catch (SecurityException e) { 216 return SEND_FILE_INFO_ERROR; 217 } 218 } 219 220 if (is == null) { 221 try { 222 is = (FileInputStream) contentResolver.openInputStream(uri); 223 224 // If the database doesn't contain the file size, get the size 225 // by reading through the entire stream 226 if (length == 0) { 227 length = getStreamSize(is); 228 // Reset the stream 229 is = (FileInputStream) contentResolver.openInputStream(uri); 230 } 231 } catch (FileNotFoundException e) { 232 return SEND_FILE_INFO_ERROR; 233 } catch (IOException e) { 234 return SEND_FILE_INFO_ERROR; 235 } 236 } 237 238 if (length == 0) { 239 Log.e(TAG, "Could not determine size of file"); 240 return SEND_FILE_INFO_ERROR; 241 } else if (length > 0xffffffffL) { 242 Log.e(TAG, "File of size: " + length + " bytes can't be transferred"); 243 throw new IllegalArgumentException(context 244 .getString(R.string.bluetooth_opp_file_limit_exceeded)); 245 } 246 247 return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0); 248 } 249 getStreamSize(FileInputStream is)250 private static long getStreamSize(FileInputStream is) throws IOException { 251 long length = 0; 252 byte[] unused = new byte[4096]; 253 int bytesRead = is.read(unused, 0, 4096); 254 while (bytesRead != -1) { 255 length += bytesRead; 256 bytesRead = is.read(unused, 0, 4096); 257 } 258 return length; 259 } 260 } 261