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