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