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.ContentValues;
37 import android.content.Context;
38 import android.database.Cursor;
39 import android.net.Uri;
40 import android.os.Environment;
41 import android.provider.MediaStore;
42 import android.util.Log;
43 
44 import java.io.UnsupportedEncodingException;
45 
46 /**
47  * This class stores information about a single receiving file. It will only be
48  * used for inbounds share, e.g. receive a file to determine a correct save file
49  * name
50  */
51 public class BluetoothOppReceiveFileInfo {
52     private static final boolean D = Constants.DEBUG;
53     private static final boolean V = Constants.VERBOSE;
54     private static String sDesiredStoragePath = null;
55 
56     /* To truncate the name of the received file if the length exceeds 245 */
57     private static final int OPP_LENGTH_OF_FILE_NAME = 244;
58 
59 
60     /** absolute store file name */
61     public String mFileName;
62 
63     public long mLength;
64 
65     public int mStatus;
66 
67     public String mData;
68 
69     public Uri mInsertUri;
70 
BluetoothOppReceiveFileInfo(String data, long length, int status)71     public BluetoothOppReceiveFileInfo(String data, long length, int status) {
72         mData = data;
73         mStatus = status;
74         mLength = length;
75     }
76 
BluetoothOppReceiveFileInfo(String filename, long length, Uri insertUri, int status)77     public BluetoothOppReceiveFileInfo(String filename, long length, Uri insertUri, int status) {
78         mFileName = filename;
79         mStatus = status;
80         mInsertUri = insertUri;
81         mLength = length;
82     }
83 
BluetoothOppReceiveFileInfo(int status)84     public BluetoothOppReceiveFileInfo(int status) {
85         this(null, 0, null, status);
86     }
87 
88     // public static final int BATCH_STATUS_CANCELED = 4;
generateFileInfo(Context context, int id)89     public static BluetoothOppReceiveFileInfo generateFileInfo(Context context, int id) {
90 
91         ContentResolver contentResolver = context.getContentResolver();
92         Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + id);
93         String filename = null, hint = null, mimeType = null;
94         long length = 0;
95         Cursor metadataCursor = contentResolver.query(contentUri, new String[]{
96                 BluetoothShare.FILENAME_HINT, BluetoothShare.TOTAL_BYTES, BluetoothShare.MIMETYPE
97         }, null, null, null);
98         if (metadataCursor != null) {
99             try {
100                 if (metadataCursor.moveToFirst()) {
101                     hint = metadataCursor.getString(0);
102                     length = metadataCursor.getLong(1);
103                     mimeType = metadataCursor.getString(2);
104                 }
105             } finally {
106                 metadataCursor.close();
107             }
108         }
109 
110         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
111             if (D) {
112                 Log.d(Constants.TAG, "Receive File aborted - no external storage");
113             }
114             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD);
115         }
116 
117         filename = choosefilename(hint);
118         if (filename == null) {
119             // should not happen. It must be pre-rejected
120             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
121         }
122         String extension = null;
123         int dotIndex = filename.lastIndexOf(".");
124         if (dotIndex < 0) {
125             if (mimeType == null) {
126                 // should not happen. It must be pre-rejected
127                 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
128             } else {
129                 extension = "";
130             }
131         } else {
132             extension = filename.substring(dotIndex);
133             filename = filename.substring(0, dotIndex);
134         }
135         if (D) {
136             Log.d(Constants.TAG, " File Name " + filename);
137         }
138 
139         if (filename.getBytes().length > OPP_LENGTH_OF_FILE_NAME) {
140           /* Including extn of the file, Linux supports 255 character as a maximum length of the
141            * file name to be created. Hence, Instead of sending OBEX_HTTP_INTERNAL_ERROR,
142            * as a response, truncate the length of the file name and save it. This check majorly
143            * helps in the case of vcard, where Phone book app supports contact name to be saved
144            * more than 255 characters, But the server rejects the card just because the length of
145            * vcf file name received exceeds 255 Characters.
146            */
147             Log.i(Constants.TAG, " File Name Length :" + filename.length());
148             Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length);
149 
150             try {
151                 byte[] oldfilename = filename.getBytes("UTF-8");
152                 byte[] newfilename = new byte[OPP_LENGTH_OF_FILE_NAME];
153                 System.arraycopy(oldfilename, 0, newfilename, 0, OPP_LENGTH_OF_FILE_NAME);
154                 filename = new String(newfilename, "UTF-8");
155             } catch (UnsupportedEncodingException e) {
156                 Log.e(Constants.TAG, "Exception: " + e);
157             }
158             if (D) {
159                 Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename);
160             }
161         }
162 
163         String fullfilename = filename + extension;
164 
165         if (V) {
166             Log.v(Constants.TAG, "Generated received filename " + fullfilename);
167         }
168 
169         Uri insertUri = null;
170         ContentValues mediaContentValues = new ContentValues();
171         mediaContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fullfilename);
172         mediaContentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
173         mediaContentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,
174                 Environment.DIRECTORY_DOWNLOADS);
175         insertUri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI,
176                 mediaContentValues);
177 
178         if (insertUri == null) {
179             if (D) {
180                 Log.e(Constants.TAG, "Error when creating file " + fullfilename);
181             }
182             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
183         }
184 
185         Log.d(Constants.TAG, "file crated, insertUri:" + insertUri.toString());
186 
187         return new BluetoothOppReceiveFileInfo(fullfilename, length, insertUri, 0);
188     }
189 
choosefilename(String hint)190     private static String choosefilename(String hint) {
191         String filename = null;
192 
193         // First, try to use the hint from the application, if there's one
194         if (filename == null && !(hint == null) && !hint.endsWith("/") && !hint.endsWith("\\")) {
195             // Prevent abuse of path backslashes by converting all backlashes '\\' chars
196             // to UNIX-style forward-slashes '/'
197             hint = hint.replace('\\', '/');
198             // Convert all whitespace characters to spaces.
199             hint = hint.replaceAll("\\s", " ");
200             // Replace illegal fat filesystem characters from the
201             // filename hint i.e. :"<>*?| with something safe.
202             hint = hint.replaceAll("[:\"<>*?|]", "_");
203             if (V) {
204                 Log.v(Constants.TAG, "getting filename from hint");
205             }
206             int index = hint.lastIndexOf('/') + 1;
207             if (index > 0) {
208                 filename = hint.substring(index);
209             } else {
210                 filename = hint;
211             }
212         }
213         return filename;
214     }
215 }
216