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.Log; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileNotFoundException; 47 import 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 */ 53 public class BluetoothOppSendFileInfo { 54 private static final String TAG = "BluetoothOppSendFileInfo"; 55 56 private static final boolean D = Constants.DEBUG; 57 58 private static final boolean V = Constants.VERBOSE; 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 */ BluetoothOppSendFileInfo(String fileName, String type, long length, FileInputStream inputStream, int status)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 */ BluetoothOppSendFileInfo(String data, String type, long length, int status)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 generateFileInfo(Context context, Uri uri, String type)100 public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri, 101 String type) { 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 } 121 if (metadataCursor != null) { 122 try { 123 if (metadataCursor.moveToFirst()) { 124 fileName = metadataCursor.getString( 125 metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 126 length = metadataCursor.getLong( 127 metadataCursor.getColumnIndex(OpenableColumns.SIZE)); 128 if (D) Log.d(TAG, "fileName = " + fileName + " length = " + length); 129 } 130 } finally { 131 metadataCursor.close(); 132 } 133 } 134 if (fileName == null) { 135 // use last segment of URI if DISPLAY_NAME query fails 136 fileName = uri.getLastPathSegment(); 137 } 138 } else if ("file".equals(scheme)) { 139 fileName = uri.getLastPathSegment(); 140 contentType = type; 141 File f = new File(uri.getPath()); 142 length = f.length(); 143 } else { 144 // currently don't accept other scheme 145 return SEND_FILE_INFO_ERROR; 146 } 147 FileInputStream is = null; 148 if (scheme.equals("content")) { 149 try { 150 // We've found that content providers don't always have the 151 // right size in _OpenableColumns.SIZE 152 // As a second source of getting the correct file length, 153 // get a file descriptor and get the stat length 154 AssetFileDescriptor fd = contentResolver.openAssetFileDescriptor(uri, "r"); 155 long statLength = fd.getLength(); 156 if (length != statLength && statLength > 0) { 157 Log.e(TAG, "Content provider length is wrong (" + Long.toString(length) + 158 "), using stat length (" + Long.toString(statLength) + ")"); 159 length = statLength; 160 } 161 162 try { 163 // This creates an auto-closing input-stream, so 164 // the file descriptor will be closed whenever the InputStream 165 // is closed. 166 is = fd.createInputStream(); 167 168 // If the database doesn't contain the file size, get the size 169 // by reading through the entire stream 170 if (length == 0) { 171 length = getStreamSize(is); 172 Log.w(TAG, "File length not provided. Length from stream = " 173 + length); 174 // Reset the stream 175 fd = contentResolver.openAssetFileDescriptor(uri, "r"); 176 is = fd.createInputStream(); 177 } 178 } catch (IOException e) { 179 try { 180 fd.close(); 181 } catch (IOException e2) { 182 // Ignore 183 } 184 } 185 } catch (FileNotFoundException e) { 186 // Ignore 187 } 188 } 189 190 if (is == null) { 191 try { 192 is = (FileInputStream) contentResolver.openInputStream(uri); 193 194 // If the database doesn't contain the file size, get the size 195 // by reading through the entire stream 196 if (length == 0) { 197 length = getStreamSize(is); 198 // Reset the stream 199 is = (FileInputStream) contentResolver.openInputStream(uri); 200 } 201 } catch (FileNotFoundException e) { 202 return SEND_FILE_INFO_ERROR; 203 } catch (IOException e) { 204 return SEND_FILE_INFO_ERROR; 205 } 206 } 207 208 if (length == 0) { 209 Log.e(TAG, "Could not determine size of file"); 210 return SEND_FILE_INFO_ERROR; 211 } 212 213 return new BluetoothOppSendFileInfo(fileName, contentType, length, is, 0); 214 } 215 getStreamSize(FileInputStream is)216 private static long getStreamSize(FileInputStream is) throws IOException { 217 long length = 0; 218 byte unused[] = new byte[4096]; 219 while (is.available() > 0) { 220 length += is.read(unused, 0, 4096); 221 } 222 return length; 223 } 224 } 225