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 com.android.bluetooth.R;
36 import com.google.android.collect.Lists;
37 
38 
39 import android.bluetooth.BluetoothAdapter;
40 import android.bluetooth.BluetoothDevice;
41 import android.net.Uri;
42 import android.content.ContentValues;
43 import android.content.Context;
44 import android.content.ActivityNotFoundException;
45 import android.content.Intent;
46 import android.content.pm.PackageManager;
47 import android.content.pm.ResolveInfo;
48 import android.database.Cursor;
49 import android.util.Log;
50 
51 import java.io.File;
52 import java.io.IOException;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.concurrent.ConcurrentHashMap;
56 
57 import android.support.v4.content.FileProvider;
58 /**
59  * This class has some utilities for Opp application;
60  */
61 public class BluetoothOppUtility {
62     private static final String TAG = "BluetoothOppUtility";
63     private static final boolean D = Constants.DEBUG;
64     private static final boolean V = Constants.VERBOSE;
65 
66     private static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap
67             = new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>();
68 
queryRecord(Context context, Uri uri)69     public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) {
70         BluetoothOppTransferInfo info = new BluetoothOppTransferInfo();
71         Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
72         if (cursor != null) {
73             if (cursor.moveToFirst()) {
74                 fillRecord(context, cursor, info);
75             }
76             cursor.close();
77         } else {
78             info = null;
79             if (V) Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri);
80         }
81         return info;
82     }
83 
fillRecord(Context context, Cursor cursor, BluetoothOppTransferInfo info)84     public static void fillRecord(Context context, Cursor cursor, BluetoothOppTransferInfo info) {
85         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
86         info.mID = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID));
87         info.mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS));
88         info.mDirection = cursor.getInt(cursor
89                 .getColumnIndexOrThrow(BluetoothShare.DIRECTION));
90         info.mTotalBytes = cursor.getLong(cursor
91                 .getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES));
92         info.mCurrentBytes = cursor.getLong(cursor
93                 .getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES));
94         info.mTimeStamp = cursor.getLong(cursor
95                 .getColumnIndexOrThrow(BluetoothShare.TIMESTAMP));
96         info.mDestAddr = cursor.getString(cursor
97                 .getColumnIndexOrThrow(BluetoothShare.DESTINATION));
98 
99         info.mFileName = cursor.getString(cursor
100                 .getColumnIndexOrThrow(BluetoothShare._DATA));
101         if (info.mFileName == null) {
102             info.mFileName = cursor.getString(cursor
103                     .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
104         }
105         if (info.mFileName == null) {
106             info.mFileName = context.getString(R.string.unknown_file);
107         }
108 
109         info.mFileUri = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI));
110 
111         if (info.mFileUri != null) {
112             Uri u = Uri.parse(info.mFileUri);
113             info.mFileType = context.getContentResolver().getType(u);
114         } else {
115             Uri u = Uri.parse(info.mFileName);
116             info.mFileType = context.getContentResolver().getType(u);
117         }
118         if (info.mFileType == null) {
119             info.mFileType = cursor.getString(cursor
120                     .getColumnIndexOrThrow(BluetoothShare.MIMETYPE));
121         }
122 
123         BluetoothDevice remoteDevice = adapter.getRemoteDevice(info.mDestAddr);
124         info.mDeviceName =
125                 BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice);
126 
127         int confirmationType = cursor.getInt(
128                 cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION));
129         info.mHandoverInitiated =
130                 confirmationType == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED;
131 
132         if (V) Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType
133                     + info.mDestAddr);
134     }
135 
136     /**
137      * Organize Array list for transfers in one batch
138      */
139     // This function is used when UI show batch transfer. Currently only show single transfer.
queryTransfersInBatch(Context context, Long timeStamp)140     public static ArrayList<String> queryTransfersInBatch(Context context, Long timeStamp) {
141         ArrayList<String> uris = Lists.newArrayList();
142         final String WHERE = BluetoothShare.TIMESTAMP + " == " + timeStamp;
143 
144         Cursor metadataCursor = context.getContentResolver().query(BluetoothShare.CONTENT_URI,
145                 new String[] {
146                     BluetoothShare._DATA
147                 }, WHERE, null, BluetoothShare._ID);
148 
149         if (metadataCursor == null) {
150             return null;
151         }
152 
153         for (metadataCursor.moveToFirst(); !metadataCursor.isAfterLast(); metadataCursor
154                 .moveToNext()) {
155             String fileName = metadataCursor.getString(0);
156             Uri path = Uri.parse(fileName);
157             // If there is no scheme, then it must be a file
158             if (path.getScheme() == null) {
159                 path = Uri.fromFile(new File(fileName));
160             }
161             uris.add(path.toString());
162             if (V) Log.d(TAG, "Uri in this batch: " + path.toString());
163         }
164         metadataCursor.close();
165         return uris;
166     }
167 
168     /**
169      * Open the received file with appropriate application, if can not find
170      * application to handle, display error dialog.
171      */
openReceivedFile(Context context, String fileName, String mimetype, Long timeStamp, Uri uri)172     public static void openReceivedFile(Context context, String fileName, String mimetype,
173             Long timeStamp, Uri uri) {
174         if (fileName == null || mimetype == null) {
175             Log.e(TAG, "ERROR: Para fileName ==null, or mimetype == null");
176             return;
177         }
178 
179         File f = new File(fileName);
180         if (!f.exists()) {
181             Intent in = new Intent(context, BluetoothOppBtErrorActivity.class);
182             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
183             in.putExtra("title", context.getString(R.string.not_exist_file));
184             in.putExtra("content", context.getString(R.string.not_exist_file_desc));
185             context.startActivity(in);
186 
187             // Due to the file is not existing, delete related info in btopp db
188             // to prevent this file from appearing in live folder
189             if (V) Log.d(TAG, "This uri will be deleted: " + uri);
190             context.getContentResolver().delete(uri, null, null);
191             return;
192         }
193 
194         Uri path = FileProvider.getUriForFile(context,
195                        "com.google.android.bluetooth.fileprovider", f);
196         // If there is no scheme, then it must be a file
197         if (path.getScheme() == null) {
198             path = Uri.fromFile(new File(fileName));
199         }
200 
201         if (isRecognizedFileType(context, path, mimetype)) {
202             Intent activityIntent = new Intent(Intent.ACTION_VIEW);
203             activityIntent.setDataAndTypeAndNormalize(path, mimetype);
204 
205             List<ResolveInfo> resInfoList = context.getPackageManager()
206                 .queryIntentActivities(activityIntent,
207                         PackageManager.MATCH_DEFAULT_ONLY);
208 
209             // Grant permissions for any app that can handle a file to access it
210             for (ResolveInfo resolveInfo : resInfoList) {
211                 String packageName = resolveInfo.activityInfo.packageName;
212                 context.grantUriPermission(packageName, path,
213                         Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
214                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
215             }
216 
217             activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
218             activityIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
219             activityIntent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
220 
221             try {
222                 if (V) Log.d(TAG, "ACTION_VIEW intent sent out: " + path + " / " + mimetype);
223                 context.startActivity(activityIntent);
224             } catch (ActivityNotFoundException ex) {
225                 if (V) Log.d(TAG, "no activity for handling ACTION_VIEW intent:  " + mimetype, ex);
226             }
227         } else {
228             Intent in = new Intent(context, BluetoothOppBtErrorActivity.class);
229             in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
230             in.putExtra("title", context.getString(R.string.unknown_file));
231             in.putExtra("content", context.getString(R.string.unknown_file_desc));
232             context.startActivity(in);
233         }
234     }
235 
236     /**
237      * To judge if the file type supported (can be handled by some app) by phone
238      * system.
239      */
isRecognizedFileType(Context context, Uri fileUri, String mimetype)240     public static boolean isRecognizedFileType(Context context, Uri fileUri, String mimetype) {
241         boolean ret = true;
242 
243         if (D) Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype);
244 
245         Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
246         mimetypeIntent.setDataAndTypeAndNormalize(fileUri, mimetype);
247         List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(mimetypeIntent,
248                 PackageManager.MATCH_DEFAULT_ONLY);
249 
250         if (list.size() == 0) {
251             if (D) Log.d(TAG, "NO application to handle MIME type " + mimetype);
252             ret = false;
253         }
254         return ret;
255     }
256 
257     /**
258      * update visibility to Hidden
259      */
updateVisibilityToHidden(Context context, Uri uri)260     public static void updateVisibilityToHidden(Context context, Uri uri) {
261         ContentValues updateValues = new ContentValues();
262         updateValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN);
263         context.getContentResolver().update(uri, updateValues, null, null);
264     }
265 
266     /**
267      * Helper function to build the progress text.
268      */
formatProgressText(long totalBytes, long currentBytes)269     public static String formatProgressText(long totalBytes, long currentBytes) {
270         if (totalBytes <= 0) {
271             return "0%";
272         }
273         long progress = currentBytes * 100 / totalBytes;
274         StringBuilder sb = new StringBuilder();
275         sb.append(progress);
276         sb.append('%');
277         return sb.toString();
278     }
279 
280     /**
281      * Get status description according to status code.
282      */
getStatusDescription(Context context, int statusCode, String deviceName)283     public static String getStatusDescription(Context context, int statusCode, String deviceName) {
284         String ret;
285         if (statusCode == BluetoothShare.STATUS_PENDING) {
286             ret = context.getString(R.string.status_pending);
287         } else if (statusCode == BluetoothShare.STATUS_RUNNING) {
288             ret = context.getString(R.string.status_running);
289         } else if (statusCode == BluetoothShare.STATUS_SUCCESS) {
290             ret = context.getString(R.string.status_success);
291         } else if (statusCode == BluetoothShare.STATUS_NOT_ACCEPTABLE) {
292             ret = context.getString(R.string.status_not_accept);
293         } else if (statusCode == BluetoothShare.STATUS_FORBIDDEN) {
294             ret = context.getString(R.string.status_forbidden);
295         } else if (statusCode == BluetoothShare.STATUS_CANCELED) {
296             ret = context.getString(R.string.status_canceled);
297         } else if (statusCode == BluetoothShare.STATUS_FILE_ERROR) {
298             ret = context.getString(R.string.status_file_error);
299         } else if (statusCode == BluetoothShare.STATUS_ERROR_NO_SDCARD) {
300             ret = context.getString(R.string.status_no_sd_card);
301         } else if (statusCode == BluetoothShare.STATUS_CONNECTION_ERROR) {
302             ret = context.getString(R.string.status_connection_error);
303         } else if (statusCode == BluetoothShare.STATUS_ERROR_SDCARD_FULL) {
304             ret = context.getString(R.string.bt_sm_2_1, deviceName);
305         } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST)
306                 || (statusCode == BluetoothShare.STATUS_LENGTH_REQUIRED)
307                 || (statusCode == BluetoothShare.STATUS_PRECONDITION_FAILED)
308                 || (statusCode == BluetoothShare.STATUS_UNHANDLED_OBEX_CODE)
309                 || (statusCode == BluetoothShare.STATUS_OBEX_DATA_ERROR)) {
310             ret = context.getString(R.string.status_protocol_error);
311         } else {
312             ret = context.getString(R.string.status_unknown_error);
313         }
314         return ret;
315     }
316 
317     /**
318      * Retry the failed transfer: Will insert a new transfer session to db
319      */
retryTransfer(Context context, BluetoothOppTransferInfo transInfo)320     public static void retryTransfer(Context context, BluetoothOppTransferInfo transInfo) {
321         ContentValues values = new ContentValues();
322         values.put(BluetoothShare.URI, transInfo.mFileUri);
323         values.put(BluetoothShare.MIMETYPE, transInfo.mFileType);
324         values.put(BluetoothShare.DESTINATION, transInfo.mDestAddr);
325 
326         final Uri contentUri = context.getContentResolver().insert(BluetoothShare.CONTENT_URI,
327                 values);
328         if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: " +
329                 transInfo.mDeviceName);
330     }
331 
putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo)332     static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) {
333         if (D) Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo);
334         sSendFileMap.put(uri, sendFileInfo);
335     }
336 
getSendFileInfo(Uri uri)337     static BluetoothOppSendFileInfo getSendFileInfo(Uri uri) {
338         if (D) Log.d(TAG, "getSendFileInfo: uri=" + uri);
339         BluetoothOppSendFileInfo info = sSendFileMap.get(uri);
340         return (info != null) ? info : BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR;
341     }
342 
closeSendFileInfo(Uri uri)343     static void closeSendFileInfo(Uri uri) {
344         if (D) Log.d(TAG, "closeSendFileInfo: uri=" + uri);
345         BluetoothOppSendFileInfo info = sSendFileMap.remove(uri);
346         if (info != null && info.mInputStream != null) {
347             try {
348                 info.mInputStream.close();
349             } catch (IOException ignored) {
350             }
351         }
352     }
353 }
354