/* * Copyright (c) 2008-2009, Motorola, Inc. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of the Motorola, Inc. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.android.bluetooth.opp; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import java.io.UnsupportedEncodingException; /** * This class stores information about a single receiving file. It will only be * used for inbounds share, e.g. receive a file to determine a correct save file * name */ public class BluetoothOppReceiveFileInfo { private static final boolean D = Constants.DEBUG; private static final boolean V = Constants.VERBOSE; private static String sDesiredStoragePath = null; /* To truncate the name of the received file if the length exceeds 245 */ private static final int OPP_LENGTH_OF_FILE_NAME = 244; /** absolute store file name */ public String mFileName; public long mLength; public int mStatus; public String mData; public Uri mInsertUri; public BluetoothOppReceiveFileInfo(String data, long length, int status) { mData = data; mStatus = status; mLength = length; } public BluetoothOppReceiveFileInfo(String filename, long length, Uri insertUri, int status) { mFileName = filename; mStatus = status; mInsertUri = insertUri; mLength = length; } public BluetoothOppReceiveFileInfo(int status) { this(null, 0, null, status); } // public static final int BATCH_STATUS_CANCELED = 4; public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) { ContentResolver contentResolver = context.getContentResolver(); Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id); String filename = null, hint = null, mimeType = null; long length = 0; Cursor metadataCursor = contentResolver.query(contentUri, new String[]{ BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE }, null, null, null); if (metadataCursor != null) { try { if (metadataCursor.moveToFirst()) { hint = metadataCursor.getString(0); length = metadataCursor.getLong(1); mimeType = metadataCursor.getString(2); } } finally { metadataCursor.close(); } } if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { if (D) { Log.d(Constants.TAG, "Receive File aborted - no external storage"); } return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD); } filename = choosefilename(hint); if (filename == null) { // should not happen. It must be pre-rejected return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } String extension = null; int dotIndex = filename.lastIndexOf("."); if (dotIndex < 0) { if (mimeType == null) { // should not happen. It must be pre-rejected return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } else { extension = ""; } } else { extension = filename.substring(dotIndex); filename = filename.substring(0, dotIndex); } if (D) { Log.d(Constants.TAG, " File Name " + filename); } if (filename.getBytes().length > OPP_LENGTH_OF_FILE_NAME) { /* Including extn of the file, Linux supports 255 character as a maximum length of the * file name to be created. Hence, Instead of sending OBEX_HTTP_INTERNAL_ERROR, * as a response, truncate the length of the file name and save it. This check majorly * helps in the case of vcard, where Phone book app supports contact name to be saved * more than 255 characters, But the server rejects the card just because the length of * vcf file name received exceeds 255 Characters. */ Log.i(Constants.TAG, " File Name Length :" + filename.length()); Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length); try { byte[] oldfilename = filename.getBytes("UTF-8"); byte[] newfilename = new byte[OPP_LENGTH_OF_FILE_NAME]; System.arraycopy(oldfilename, 0, newfilename, 0, OPP_LENGTH_OF_FILE_NAME); filename = new String(newfilename, "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(Constants.TAG, "Exception: " + e); } if (D) { Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename); } } String fullfilename = filename + extension; if (V) { Log.v(Constants.TAG, "Generated received filename " + fullfilename); } Uri insertUri = null; ContentValues mediaContentValues = new ContentValues(); mediaContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fullfilename); mediaContentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); mediaContentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS); insertUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, mediaContentValues); if (insertUri == null) { if (D) { Log.e(Constants.TAG, "Error when creating file " + fullfilename); } return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR); } Log.d(Constants.TAG, "file crated, insertUri:" + insertUri.toString()); return new BluetoothOppReceiveFileInfo(fullfilename, length, insertUri, 0); } private static String choosefilename(String hint) { String filename = null; // First, try to use the hint from the application, if there's one if (filename == null && !(hint == null) && !hint.endsWith("/") && !hint.endsWith("\\")) { // Prevent abuse of path backslashes by converting all backlashes '\\' chars // to UNIX-style forward-slashes '/' hint = hint.replace('\\', '/'); // Convert all whitespace characters to spaces. hint = hint.replaceAll("\\s", " "); // Replace illegal fat filesystem characters from the // filename hint i.e. :"<>*?| with something safe. hint = hint.replaceAll("[:\"<>*?|]", "_"); if (V) { Log.v(Constants.TAG, "getting filename from hint"); } int index = hint.lastIndexOf('/') + 1; if (index > 0) { filename = hint.substring(index); } else { filename = hint; } } return filename; } }