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