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