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