• 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.bluetooth.BluetoothProfile;
36 import android.bluetooth.BluetoothProtoEnums;
37 import android.content.ContentResolver;
38 import android.content.ContentValues;
39 import android.content.Context;
40 import android.database.Cursor;
41 import android.net.Uri;
42 import android.os.Environment;
43 import android.provider.MediaStore;
44 import android.util.Log;
45 
46 import com.android.bluetooth.BluetoothMethodProxy;
47 import com.android.bluetooth.BluetoothStatsLog;
48 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils;
49 
50 import java.nio.charset.StandardCharsets;
51 import java.text.DateFormat;
52 import java.text.SimpleDateFormat;
53 import java.util.Calendar;
54 import java.util.Locale;
55 
56 /**
57  * This class stores information about a single receiving file. It will only be used for inbounds
58  * share, e.g. receive a file to determine a correct save file name
59  */
60 // Next tag value for ContentProfileErrorReportUtils.report(): 2
61 public class BluetoothOppReceiveFileInfo {
62     /* To truncate the name of the received file if the length exceeds 237 */
63     private static final int OPP_LENGTH_OF_FILE_NAME = 237;
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 hint = null, mimeType = null;
99         long length = 0;
100         Cursor metadataCursor =
101                 BluetoothMethodProxy.getInstance()
102                         .contentResolverQuery(
103                                 contentResolver,
104                                 contentUri,
105                                 new String[] {
106                                     BluetoothShare.FILENAME_HINT,
107                                     BluetoothShare.TOTAL_BYTES,
108                                     BluetoothShare.MIMETYPE
109                                 },
110                                 null,
111                                 null,
112                                 null);
113         if (metadataCursor != null) {
114             try {
115                 if (metadataCursor.moveToFirst()) {
116                     hint = metadataCursor.getString(0);
117                     length = metadataCursor.getLong(1);
118                     mimeType = metadataCursor.getString(2);
119                 }
120             } finally {
121                 metadataCursor.close();
122             }
123         }
124 
125         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
126             Log.d(Constants.TAG, "Receive File aborted - no external storage");
127             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_ERROR_NO_SDCARD);
128         }
129 
130         String filename = chooseFilename(hint);
131         if (filename == null) {
132             // should not happen. It must be pre-rejected
133             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
134         }
135         String extension = null;
136         int dotIndex = filename.lastIndexOf(".");
137         if (dotIndex < 0) {
138             if (mimeType == null) {
139                 // should not happen. It must be pre-rejected
140                 return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
141             } else {
142                 extension = "";
143             }
144         } else {
145             extension = filename.substring(dotIndex);
146             filename = filename.substring(0, dotIndex);
147         }
148         Log.d(Constants.TAG, " File Name " + filename);
149 
150         if (filename.getBytes().length > OPP_LENGTH_OF_FILE_NAME) {
151             /* Including extn of the file, Linux supports 255 character as a maximum length of the
152              * file name to be created. Hence, Instead of sending OBEX_HTTP_INTERNAL_ERROR,
153              * as a response, truncate the length of the file name and save it. This check majorly
154              * helps in the case of vcard, where Phone book app supports contact name to be saved
155              * more than 255 characters, But the server rejects the card just because the length of
156              * vcf file name received exceeds 255 Characters.
157              */
158             Log.i(Constants.TAG, " File Name Length :" + filename.length());
159             Log.i(Constants.TAG, " File Name Length in Bytes:" + filename.getBytes().length);
160 
161             byte[] oldFilename = filename.getBytes(StandardCharsets.UTF_8);
162             byte[] newFilename = new byte[OPP_LENGTH_OF_FILE_NAME];
163             System.arraycopy(oldFilename, 0, newFilename, 0, OPP_LENGTH_OF_FILE_NAME);
164             filename = new String(newFilename, StandardCharsets.UTF_8);
165             Log.d(Constants.TAG, "File name is too long. Name is truncated as: " + filename);
166         }
167 
168         DateFormat dateFormat = new SimpleDateFormat("_hhmmss", Locale.ROOT);
169         String currentTime = dateFormat.format(Calendar.getInstance().getTime());
170         String fullFilename = filename + currentTime + extension;
171 
172         Log.v(Constants.TAG, "Generated received filename " + fullFilename);
173 
174         ContentValues mediaContentValues = new ContentValues();
175         mediaContentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fullFilename);
176         mediaContentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
177         mediaContentValues.put(
178                 MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
179         Uri insertUri =
180                 BluetoothMethodProxy.getInstance()
181                         .contentResolverInsert(
182                                 contentResolver,
183                                 MediaStore.Downloads.EXTERNAL_CONTENT_URI,
184                                 mediaContentValues);
185 
186         if (insertUri == null) {
187             Log.e(Constants.TAG, "Error when creating file " + fullFilename);
188             ContentProfileErrorReportUtils.report(
189                     BluetoothProfile.OPP,
190                     BluetoothProtoEnums.BLUETOOTH_OPP_RECEIVE_FILE_INFO,
191                     BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR,
192                     1);
193             return new BluetoothOppReceiveFileInfo(BluetoothShare.STATUS_FILE_ERROR);
194         }
195 
196         Log.d(Constants.TAG, "file crated, insertUri:" + insertUri.toString());
197 
198         return new BluetoothOppReceiveFileInfo(fullFilename, length, insertUri, 0);
199     }
200 
chooseFilename(String hint)201     private static String chooseFilename(String hint) {
202         String filename = null;
203 
204         // First, try to use the hint from the application, if there's one
205         if (filename == null && !(hint == null) && !hint.endsWith("/") && !hint.endsWith("\\")) {
206             // Prevent abuse of path backslashes by converting all backlashes '\\' chars
207             // to UNIX-style forward-slashes '/'
208             hint = hint.replace('\\', '/');
209             // Convert all whitespace characters to spaces.
210             hint = hint.replaceAll("\\s", " ");
211             // Replace illegal fat filesystem characters from the
212             // filename hint i.e. :"<>*?| with something safe.
213             hint = hint.replaceAll("[:\"<>*?|]", "_");
214             Log.v(Constants.TAG, "getting filename from hint");
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