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.app.NotificationManager; 36 import android.bluetooth.BluetoothAdapter; 37 import android.bluetooth.BluetoothDevice; 38 import android.bluetooth.BluetoothProfile; 39 import android.bluetooth.BluetoothProtoEnums; 40 import android.content.ActivityNotFoundException; 41 import android.content.ContentResolver; 42 import android.content.ContentValues; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.pm.PackageManager; 46 import android.content.pm.ResolveInfo; 47 import android.database.Cursor; 48 import android.icu.text.MessageFormat; 49 import android.net.Uri; 50 import android.os.Environment; 51 import android.os.ParcelFileDescriptor; 52 import android.os.SystemProperties; 53 import android.util.EventLog; 54 import android.util.Log; 55 56 import com.android.bluetooth.BluetoothMethodProxy; 57 import com.android.bluetooth.BluetoothStatsLog; 58 import com.android.bluetooth.R; 59 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 60 import com.android.internal.annotations.VisibleForTesting; 61 62 import java.io.File; 63 import java.io.IOException; 64 import java.math.RoundingMode; 65 import java.text.DecimalFormat; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.HashMap; 69 import java.util.List; 70 import java.util.Locale; 71 import java.util.Map; 72 import java.util.Objects; 73 import java.util.concurrent.ConcurrentHashMap; 74 75 /** This class has some utilities for Opp application; */ 76 // Next tag value for ContentProfileErrorReportUtils.report(): 10 77 public class BluetoothOppUtility { 78 private static final String TAG = "BluetoothOppUtility"; 79 80 /** Whether the device has the "nosdcard" characteristic, or null if not-yet-known. */ 81 private static Boolean sNoSdCard = null; 82 83 @VisibleForTesting 84 static final ConcurrentHashMap<Uri, BluetoothOppSendFileInfo> sSendFileMap = 85 new ConcurrentHashMap<Uri, BluetoothOppSendFileInfo>(); 86 isBluetoothShareUri(Uri uri)87 public static boolean isBluetoothShareUri(Uri uri) { 88 if (uri.toString().startsWith(BluetoothShare.CONTENT_URI.toString()) 89 && !uri.getAuthority().equals(BluetoothShare.CONTENT_URI.getAuthority())) { 90 EventLog.writeEvent(0x534e4554, "225880741", -1, ""); 91 } 92 return Objects.equals(uri.getAuthority(), BluetoothShare.CONTENT_URI.getAuthority()); 93 } 94 queryRecord(Context context, Uri uri)95 public static BluetoothOppTransferInfo queryRecord(Context context, Uri uri) { 96 BluetoothOppTransferInfo info = new BluetoothOppTransferInfo(); 97 Cursor cursor = 98 BluetoothMethodProxy.getInstance() 99 .contentResolverQuery( 100 context.getContentResolver(), uri, null, null, null, null); 101 if (cursor != null) { 102 if (cursor.moveToFirst()) { 103 fillRecord(context, cursor, info); 104 } 105 cursor.close(); 106 } else { 107 info = null; 108 Log.v(TAG, "BluetoothOppManager Error: not got data from db for uri:" + uri); 109 } 110 return info; 111 } 112 fillRecord(Context context, Cursor cursor, BluetoothOppTransferInfo info)113 public static void fillRecord(Context context, Cursor cursor, BluetoothOppTransferInfo info) { 114 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 115 info.mID = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare._ID)); 116 info.mStatus = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.STATUS)); 117 info.mDirection = cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.DIRECTION)); 118 info.mTotalBytes = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TOTAL_BYTES)); 119 info.mCurrentBytes = 120 cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.CURRENT_BYTES)); 121 info.mTimeStamp = cursor.getLong(cursor.getColumnIndexOrThrow(BluetoothShare.TIMESTAMP)); 122 info.mDestAddr = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.DESTINATION)); 123 124 info.mFileName = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare._DATA)); 125 if (info.mFileName == null) { 126 info.mFileName = 127 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT)); 128 } 129 if (info.mFileName == null) { 130 info.mFileName = context.getString(R.string.unknown_file); 131 } 132 133 info.mFileUri = cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.URI)); 134 135 if (info.mFileUri != null) { 136 Uri u = Uri.parse(info.mFileUri); 137 info.mFileType = context.getContentResolver().getType(u); 138 } else { 139 Uri u = Uri.parse(info.mFileName); 140 info.mFileType = context.getContentResolver().getType(u); 141 } 142 if (info.mFileType == null) { 143 info.mFileType = 144 cursor.getString(cursor.getColumnIndexOrThrow(BluetoothShare.MIMETYPE)); 145 } 146 147 BluetoothDevice remoteDevice = adapter.getRemoteDevice(info.mDestAddr); 148 info.mDeviceName = BluetoothOppManager.getInstance(context).getDeviceName(remoteDevice); 149 150 int confirmationType = 151 cursor.getInt(cursor.getColumnIndexOrThrow(BluetoothShare.USER_CONFIRMATION)); 152 info.mHandoverInitiated = 153 confirmationType == BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED; 154 155 Log.v(TAG, "Get data from db:" + info.mFileName + info.mFileType + info.mDestAddr); 156 } 157 158 /** Organize Array list for transfers in one batch */ 159 // This function is used when UI show batch transfer. Currently only show single transfer. queryTransfersInBatch(Context context, Long timeStamp)160 public static ArrayList<String> queryTransfersInBatch(Context context, Long timeStamp) { 161 ArrayList<String> uris = new ArrayList(); 162 final String where = BluetoothShare.TIMESTAMP + " == " + timeStamp; 163 Cursor metadataCursor = 164 BluetoothMethodProxy.getInstance() 165 .contentResolverQuery( 166 context.getContentResolver(), 167 BluetoothShare.CONTENT_URI, 168 new String[] {BluetoothShare._DATA}, 169 where, 170 null, 171 BluetoothShare._ID); 172 173 if (metadataCursor == null) { 174 return null; 175 } 176 177 for (metadataCursor.moveToFirst(); 178 !metadataCursor.isAfterLast(); 179 metadataCursor.moveToNext()) { 180 String fileName = metadataCursor.getString(0); 181 Uri path = Uri.parse(fileName); 182 // If there is no scheme, then it must be a file 183 if (path.getScheme() == null) { 184 path = Uri.fromFile(new File(fileName)); 185 } 186 uris.add(path.toString()); 187 Log.v(TAG, "Uri in this batch: " + path.toString()); 188 } 189 metadataCursor.close(); 190 return uris; 191 } 192 193 /** 194 * Open the received file with appropriate application, if can not find application to handle, 195 * display error dialog. 196 */ openReceivedFile( Context context, String fileName, String mimetype, Long timeStamp, Uri uri)197 public static void openReceivedFile( 198 Context context, String fileName, String mimetype, Long timeStamp, Uri uri) { 199 if (fileName == null || mimetype == null) { 200 Log.e(TAG, "ERROR: Para fileName ==null, or mimetype == null"); 201 ContentProfileErrorReportUtils.report( 202 BluetoothProfile.OPP, 203 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 204 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 205 0); 206 return; 207 } 208 209 if (!isBluetoothShareUri(uri)) { 210 Log.e(TAG, "Trying to open a file that wasn't transfered over Bluetooth"); 211 ContentProfileErrorReportUtils.report( 212 BluetoothProfile.OPP, 213 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 214 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 215 1); 216 return; 217 } 218 219 Uri path = null; 220 Cursor metadataCursor = 221 BluetoothMethodProxy.getInstance() 222 .contentResolverQuery( 223 context.getContentResolver(), 224 uri, 225 new String[] {BluetoothShare.URI}, 226 null, 227 null, 228 null); 229 if (metadataCursor != null) { 230 try { 231 if (metadataCursor.moveToFirst()) { 232 path = Uri.parse(metadataCursor.getString(0)); 233 } 234 } finally { 235 metadataCursor.close(); 236 } 237 } 238 239 if (path == null) { 240 Log.e(TAG, "file uri not exist"); 241 ContentProfileErrorReportUtils.report( 242 BluetoothProfile.OPP, 243 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 244 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 245 2); 246 return; 247 } 248 249 if (!fileExists(context, path)) { 250 Intent in = new Intent(context, BluetoothOppBtErrorActivity.class); 251 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 252 in.putExtra("title", context.getString(R.string.not_exist_file)); 253 in.putExtra("content", context.getString(R.string.not_exist_file_desc)); 254 context.startActivity(in); 255 256 // Due to the file is not existing, delete related info in btopp db 257 // to prevent this file from appearing in live folder 258 Log.v(TAG, "This uri will be deleted: " + uri); 259 BluetoothMethodProxy.getInstance() 260 .contentResolverDelete(context.getContentResolver(), uri, null, null); 261 return; 262 } 263 264 if (isRecognizedFileType(context, path, mimetype)) { 265 Intent activityIntent = new Intent(Intent.ACTION_VIEW); 266 activityIntent.setDataAndTypeAndNormalize(path, mimetype); 267 activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 268 activityIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 269 270 try { 271 Log.v(TAG, "ACTION_VIEW intent sent out: " + path + " / " + mimetype); 272 context.startActivity(activityIntent); 273 } catch (ActivityNotFoundException ex) { 274 ContentProfileErrorReportUtils.report( 275 BluetoothProfile.OPP, 276 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 277 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 278 3); 279 Log.v(TAG, "no activity for handling ACTION_VIEW intent: " + mimetype, ex); 280 } 281 } else { 282 Intent in = new Intent(context, BluetoothOppBtErrorActivity.class); 283 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 284 in.putExtra("title", context.getString(R.string.unknown_file)); 285 in.putExtra("content", context.getString(R.string.unknown_file_desc)); 286 context.startActivity(in); 287 } 288 } 289 fileExists(Context context, Uri uri)290 static boolean fileExists(Context context, Uri uri) { 291 // Open a specific media item using ParcelFileDescriptor. 292 ContentResolver resolver = context.getContentResolver(); 293 String readOnlyMode = "r"; 294 try (ParcelFileDescriptor unusedPfd = 295 BluetoothMethodProxy.getInstance() 296 .contentResolverOpenFileDescriptor(resolver, uri, readOnlyMode)) { 297 return true; 298 } catch (IOException e) { 299 ContentProfileErrorReportUtils.report( 300 BluetoothProfile.OPP, 301 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 302 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 303 4); 304 e.printStackTrace(); 305 } 306 return false; 307 } 308 309 /** To judge if the file type supported (can be handled by some app) by phone system. */ isRecognizedFileType(Context context, Uri fileUri, String mimetype)310 public static boolean isRecognizedFileType(Context context, Uri fileUri, String mimetype) { 311 boolean ret = true; 312 313 Log.d(TAG, "RecognizedFileType() fileUri: " + fileUri + " mimetype: " + mimetype); 314 315 Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW); 316 mimetypeIntent.setDataAndTypeAndNormalize(fileUri, mimetype); 317 List<ResolveInfo> list = 318 context.getPackageManager() 319 .queryIntentActivities(mimetypeIntent, PackageManager.MATCH_DEFAULT_ONLY); 320 321 if (list.size() == 0) { 322 Log.d(TAG, "NO application to handle MIME type " + mimetype); 323 ret = false; 324 } 325 return ret; 326 } 327 328 /** update visibility to Hidden */ updateVisibilityToHidden(Context context, Uri uri)329 public static void updateVisibilityToHidden(Context context, Uri uri) { 330 ContentValues updateValues = new ContentValues(); 331 updateValues.put(BluetoothShare.VISIBILITY, BluetoothShare.VISIBILITY_HIDDEN); 332 BluetoothMethodProxy.getInstance() 333 .contentResolverUpdate(context.getContentResolver(), uri, updateValues, null, null); 334 } 335 336 /** Helper function to build the progress text. */ formatProgressText(long totalBytes, long currentBytes)337 public static String formatProgressText(long totalBytes, long currentBytes) { 338 DecimalFormat df = new DecimalFormat("0%"); 339 df.setRoundingMode(RoundingMode.DOWN); 340 double percent = 0.0; 341 if (totalBytes > 0) { 342 percent = currentBytes / (double) totalBytes; 343 } 344 return df.format(percent); 345 } 346 347 /** Helper function to build the result notification text content. */ formatResultText(int countSuccess, int countUnsuccessful, Context context)348 static String formatResultText(int countSuccess, int countUnsuccessful, Context context) { 349 if (context == null) { 350 return null; 351 } 352 Map<String, Object> mapUnsuccessful = new HashMap<>(); 353 mapUnsuccessful.put("count", countUnsuccessful); 354 355 Map<String, Object> mapSuccess = new HashMap<>(); 356 mapSuccess.put("count", countSuccess); 357 358 return new MessageFormat( 359 context.getResources() 360 .getString( 361 R.string.noti_caption_success, 362 new MessageFormat( 363 context.getResources() 364 .getString( 365 R.string 366 .noti_caption_unsuccessful), 367 Locale.getDefault()) 368 .format(mapUnsuccessful)), 369 Locale.getDefault()) 370 .format(mapSuccess); 371 } 372 373 /** Whether the device has the "nosdcard" characteristic or not. */ deviceHasNoSdCard()374 public static boolean deviceHasNoSdCard() { 375 if (sNoSdCard == null) { 376 String characteristics = SystemProperties.get("ro.build.characteristics", ""); 377 sNoSdCard = Arrays.asList(characteristics).contains("nosdcard"); 378 } 379 return sNoSdCard; 380 } 381 382 /** Get status description according to status code. */ getStatusDescription(Context context, int statusCode, String deviceName)383 public static String getStatusDescription(Context context, int statusCode, String deviceName) { 384 String ret; 385 if (statusCode == BluetoothShare.STATUS_PENDING) { 386 ret = context.getString(R.string.status_pending); 387 } else if (statusCode == BluetoothShare.STATUS_RUNNING) { 388 ret = context.getString(R.string.status_running); 389 } else if (statusCode == BluetoothShare.STATUS_SUCCESS) { 390 ret = context.getString(R.string.status_success); 391 } else if (statusCode == BluetoothShare.STATUS_NOT_ACCEPTABLE) { 392 ret = context.getString(R.string.status_not_accept); 393 } else if (statusCode == BluetoothShare.STATUS_FORBIDDEN) { 394 ret = context.getString(R.string.status_forbidden); 395 } else if (statusCode == BluetoothShare.STATUS_CANCELED) { 396 ret = context.getString(R.string.status_canceled); 397 } else if (statusCode == BluetoothShare.STATUS_FILE_ERROR) { 398 ret = context.getString(R.string.status_file_error); 399 } else if (statusCode == BluetoothShare.STATUS_ERROR_NO_SDCARD) { 400 int id = 401 deviceHasNoSdCard() 402 ? R.string.status_no_sd_card_nosdcard 403 : R.string.status_no_sd_card_default; 404 ret = context.getString(id); 405 } else if (statusCode == BluetoothShare.STATUS_CONNECTION_ERROR) { 406 ret = context.getString(R.string.status_connection_error); 407 } else if (statusCode == BluetoothShare.STATUS_ERROR_SDCARD_FULL) { 408 int id = deviceHasNoSdCard() ? R.string.bt_sm_2_1_nosdcard : R.string.bt_sm_2_1_default; 409 ret = context.getString(id); 410 } else if ((statusCode == BluetoothShare.STATUS_BAD_REQUEST) 411 || (statusCode == BluetoothShare.STATUS_LENGTH_REQUIRED) 412 || (statusCode == BluetoothShare.STATUS_PRECONDITION_FAILED) 413 || (statusCode == BluetoothShare.STATUS_UNHANDLED_OBEX_CODE) 414 || (statusCode == BluetoothShare.STATUS_OBEX_DATA_ERROR)) { 415 ret = context.getString(R.string.status_protocol_error); 416 } else { 417 ret = context.getString(R.string.status_unknown_error); 418 } 419 return ret; 420 } 421 422 /** Retry the failed transfer: Will insert a new transfer session to db */ retryTransfer(Context context, BluetoothOppTransferInfo transInfo)423 public static void retryTransfer(Context context, BluetoothOppTransferInfo transInfo) { 424 ContentValues values = new ContentValues(); 425 values.put(BluetoothShare.URI, transInfo.mFileUri); 426 values.put(BluetoothShare.MIMETYPE, transInfo.mFileType); 427 values.put(BluetoothShare.DESTINATION, transInfo.mDestAddr); 428 429 final Uri contentUri = 430 context.getContentResolver().insert(BluetoothShare.CONTENT_URI, values); 431 Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " + transInfo.mDeviceName); 432 } 433 originalUri(Uri uri)434 static Uri originalUri(Uri uri) { 435 String mUri = uri.toString(); 436 int atIndex = mUri.lastIndexOf("@"); 437 if (atIndex != -1) { 438 mUri = mUri.substring(0, atIndex); 439 uri = Uri.parse(mUri); 440 } 441 Log.v(TAG, "originalUri: " + uri); 442 return uri; 443 } 444 generateUri(Uri uri, BluetoothOppSendFileInfo sendFileInfo)445 static Uri generateUri(Uri uri, BluetoothOppSendFileInfo sendFileInfo) { 446 String fileInfo = sendFileInfo.toString(); 447 int atIndex = fileInfo.lastIndexOf("@"); 448 fileInfo = fileInfo.substring(atIndex); 449 uri = Uri.parse(uri + fileInfo); 450 Log.v(TAG, "generateUri: " + uri); 451 return uri; 452 } 453 putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo)454 static void putSendFileInfo(Uri uri, BluetoothOppSendFileInfo sendFileInfo) { 455 Log.d(TAG, "putSendFileInfo: uri=" + uri + " sendFileInfo=" + sendFileInfo); 456 if (sendFileInfo == BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR) { 457 Log.e(TAG, "putSendFileInfo: bad sendFileInfo, URI: " + uri); 458 ContentProfileErrorReportUtils.report( 459 BluetoothProfile.OPP, 460 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 461 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 462 5); 463 } 464 sSendFileMap.put(uri, sendFileInfo); 465 } 466 getSendFileInfo(Uri uri)467 static BluetoothOppSendFileInfo getSendFileInfo(Uri uri) { 468 Log.d(TAG, "getSendFileInfo: uri=" + uri); 469 BluetoothOppSendFileInfo info = sSendFileMap.get(uri); 470 return (info != null) ? info : BluetoothOppSendFileInfo.SEND_FILE_INFO_ERROR; 471 } 472 closeSendFileInfo(Uri uri)473 static void closeSendFileInfo(Uri uri) { 474 Log.d(TAG, "closeSendFileInfo: uri=" + uri); 475 BluetoothOppSendFileInfo info = sSendFileMap.remove(uri); 476 if (info != null && info.mInputStream != null) { 477 try { 478 info.mInputStream.close(); 479 } catch (IOException ignored) { 480 ContentProfileErrorReportUtils.report( 481 BluetoothProfile.OPP, 482 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 483 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 484 6); 485 } 486 } 487 } 488 489 /** 490 * Checks if the URI is in Environment.getExternalStorageDirectory() as it is the only directory 491 * that is possibly readable by both the sender and the Bluetooth process. 492 */ isInExternalStorageDir(Uri uri)493 static boolean isInExternalStorageDir(Uri uri) { 494 if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { 495 Log.e(TAG, "Not a file URI: " + uri); 496 ContentProfileErrorReportUtils.report( 497 BluetoothProfile.OPP, 498 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 499 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 500 7); 501 return false; 502 } 503 504 if ("file".equals(uri.getScheme())) { 505 String canonicalPath; 506 try { 507 canonicalPath = new File(uri.getPath()).getCanonicalPath(); 508 } catch (IOException e) { 509 ContentProfileErrorReportUtils.report( 510 BluetoothProfile.OPP, 511 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 512 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 513 8); 514 canonicalPath = uri.getPath(); 515 } 516 File file = new File(canonicalPath); 517 // if emulated 518 if (Environment.isExternalStorageEmulated()) { 519 // Gets legacy external storage path 520 final String legacyPath = new File(System.getenv("EXTERNAL_STORAGE")).toString(); 521 // Splice in user-specific path when legacy path is found 522 if (canonicalPath.startsWith(legacyPath)) { 523 file = 524 new File( 525 Environment.getExternalStorageDirectory().toString(), 526 canonicalPath.substring(legacyPath.length() + 1)); 527 } 528 } 529 return isSameOrSubDirectory(Environment.getExternalStorageDirectory(), file); 530 } 531 return isSameOrSubDirectory( 532 Environment.getExternalStorageDirectory(), new File(uri.getPath())); 533 } 534 isForbiddenContent(Uri uri)535 static boolean isForbiddenContent(Uri uri) { 536 if ("com.android.bluetooth.map.MmsFileProvider".equals(uri.getHost())) { 537 return true; 538 } 539 return false; 540 } 541 542 /** 543 * Checks, whether the child directory is the same as, or a sub-directory of the base directory. 544 * Neither base nor child should be null. 545 */ isSameOrSubDirectory(File base, File child)546 static boolean isSameOrSubDirectory(File base, File child) { 547 try { 548 base = base.getCanonicalFile(); 549 child = child.getCanonicalFile(); 550 File parentFile = child; 551 while (parentFile != null) { 552 if (base.equals(parentFile)) { 553 return true; 554 } 555 parentFile = parentFile.getParentFile(); 556 } 557 return false; 558 } catch (IOException ex) { 559 ContentProfileErrorReportUtils.report( 560 BluetoothProfile.OPP, 561 BluetoothProtoEnums.BLUETOOTH_OPP_UTILITY, 562 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__EXCEPTION, 563 9); 564 Log.e(TAG, "Error while accessing file", ex); 565 return false; 566 } 567 } 568 cancelNotification(Context ctx)569 protected static void cancelNotification(Context ctx) { 570 NotificationManager nm = ctx.getSystemService(NotificationManager.class); 571 nm.cancel(BluetoothOppNotification.NOTIFICATION_ID_PROGRESS); 572 } 573 } 574