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