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.BluetoothAdapter; 36 import android.bluetooth.BluetoothDevice; 37 import android.bluetooth.BluetoothProfile; 38 import android.bluetooth.BluetoothProtoEnums; 39 import android.content.ContentResolver; 40 import android.content.ContentValues; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.SharedPreferences; 44 import android.net.Uri; 45 import android.os.Process; 46 import android.os.SystemClock; 47 import android.text.TextUtils; 48 import android.util.Log; 49 import android.util.Pair; 50 51 import com.android.bluetooth.BluetoothMethodProxy; 52 import com.android.bluetooth.BluetoothStatsLog; 53 import com.android.bluetooth.R; 54 import com.android.bluetooth.Utils; 55 import com.android.bluetooth.content_profiles.ContentProfileErrorReportUtils; 56 import com.android.bluetooth.flags.Flags; 57 import com.android.internal.annotations.VisibleForTesting; 58 59 import java.util.ArrayList; 60 import java.util.Iterator; 61 import java.util.List; 62 63 /** 64 * This class provides a simplified interface on top of other Bluetooth service layer components; 65 * Also it handles some Opp application level variables. It's a singleton got from 66 * BluetoothOppManager.getInstance(context); 67 */ 68 // Next tag value for ContentProfileErrorReportUtils.report(): 2 69 public class BluetoothOppManager { 70 private static final String TAG = "BluetoothOppManager"; 71 72 @VisibleForTesting static BluetoothOppManager sInstance; 73 74 /** Used when obtaining a reference to the singleton instance. */ 75 private static final Object INSTANCE_LOCK = new Object(); 76 77 private boolean mInitialized; 78 79 private Context mContext; 80 81 private BluetoothAdapter mAdapter; 82 83 @VisibleForTesting String mMimeTypeOfSendingFile; 84 85 @VisibleForTesting String mUriOfSendingFile; 86 87 @VisibleForTesting String mMimeTypeOfSendingFiles; 88 89 @VisibleForTesting ArrayList<Uri> mUrisOfSendingFiles; 90 91 private boolean mIsHandoverInitiated; 92 93 @VisibleForTesting static final String OPP_PREFERENCE_FILE = "OPPMGR"; 94 95 private static final String SENDING_FLAG = "SENDINGFLAG"; 96 97 private static final String MIME_TYPE = "MIMETYPE"; 98 99 private static final String FILE_URI = "FILE_URI"; 100 101 private static final String MIME_TYPE_MULTIPLE = "MIMETYPE_MULTIPLE"; 102 103 private static final String FILE_URIS = "FILE_URIS"; 104 105 private static final String MULTIPLE_FLAG = "MULTIPLE_FLAG"; 106 107 private static final String ARRAYLIST_ITEM_SEPERATOR = ";"; 108 109 @VisibleForTesting static final int ALLOWED_INSERT_SHARE_THREAD_NUMBER = 3; 110 111 // used to judge if need continue sending process after received a 112 // ENABLED_ACTION 113 public boolean mSendingFlag; 114 115 public boolean mMultipleFlag; 116 117 private int mFileNumInBatch; 118 119 private int mInsertShareThreadNum = 0; 120 121 // A list of devices that may send files over OPP to this device 122 // without user confirmation. Used for connection handover from forex NFC. 123 private List<Pair<String, Long>> mAcceptlist = new ArrayList<Pair<String, Long>>(); 124 125 // The time for which the acceptlist entries remain valid. 126 private static final int ACCEPTLIST_DURATION_MS = 15000; 127 128 /** Get singleton instance. */ getInstance(Context context)129 public static BluetoothOppManager getInstance(Context context) { 130 synchronized (INSTANCE_LOCK) { 131 if (sInstance == null) { 132 sInstance = new BluetoothOppManager(); 133 } 134 sInstance.init(context); 135 136 return sInstance; 137 } 138 } 139 140 /** Set Singleton instance. Intended for testing purpose */ 141 @VisibleForTesting setInstance(BluetoothOppManager instance)142 static void setInstance(BluetoothOppManager instance) { 143 sInstance = instance; 144 } 145 146 /** init */ init(Context context)147 private boolean init(Context context) { 148 if (mInitialized) { 149 return true; 150 } 151 mInitialized = true; 152 153 mContext = context; 154 155 mAdapter = BluetoothAdapter.getDefaultAdapter(); 156 if (mAdapter == null) { 157 Log.v(TAG, "BLUETOOTH_SERVICE is not started! "); 158 } 159 160 // Restore data from preference 161 restoreApplicationData(); 162 163 return true; 164 } 165 cleanupAcceptlist()166 private void cleanupAcceptlist() { 167 // Removes expired entries 168 long curTime = SystemClock.elapsedRealtime(); 169 for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) { 170 Pair<String, Long> entry = iter.next(); 171 if (curTime - entry.second > ACCEPTLIST_DURATION_MS) { 172 Log.v(TAG, "Cleaning out acceptlist entry " + entry.first); 173 iter.remove(); 174 } 175 } 176 } 177 addToAcceptlist(String address)178 public synchronized void addToAcceptlist(String address) { 179 if (address == null) { 180 return; 181 } 182 // Remove any existing entries 183 for (Iterator<Pair<String, Long>> iter = mAcceptlist.iterator(); iter.hasNext(); ) { 184 Pair<String, Long> entry = iter.next(); 185 if (entry.first.equals(address)) { 186 iter.remove(); 187 } 188 } 189 mAcceptlist.add(new Pair<String, Long>(address, SystemClock.elapsedRealtime())); 190 } 191 isAcceptlisted(String address)192 public synchronized boolean isAcceptlisted(String address) { 193 cleanupAcceptlist(); 194 for (Pair<String, Long> entry : mAcceptlist) { 195 if (entry.first.equals(address)) { 196 return true; 197 } 198 } 199 return false; 200 } 201 202 /** Restore data from preference */ restoreApplicationData()203 private void restoreApplicationData() { 204 SharedPreferences settings = mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0); 205 206 // All member vars are not initialized till now 207 mSendingFlag = settings.getBoolean(SENDING_FLAG, false); 208 mMimeTypeOfSendingFile = settings.getString(MIME_TYPE, null); 209 mUriOfSendingFile = settings.getString(FILE_URI, null); 210 mMimeTypeOfSendingFiles = settings.getString(MIME_TYPE_MULTIPLE, null); 211 mMultipleFlag = settings.getBoolean(MULTIPLE_FLAG, false); 212 213 Log.v( 214 TAG, 215 "restoreApplicationData! " 216 + mSendingFlag 217 + mMultipleFlag 218 + mMimeTypeOfSendingFile 219 + mUriOfSendingFile); 220 221 String strUris = settings.getString(FILE_URIS, null); 222 mUrisOfSendingFiles = new ArrayList<Uri>(); 223 if (strUris != null) { 224 String[] splitUri = strUris.split(ARRAYLIST_ITEM_SEPERATOR); 225 for (int i = 0; i < splitUri.length; i++) { 226 mUrisOfSendingFiles.add(Uri.parse(splitUri[i])); 227 Log.v(TAG, "Uri in batch: " + Uri.parse(splitUri[i])); 228 } 229 } 230 231 mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit().clear().apply(); 232 } 233 234 /** Save application data to preference, need restore these data when service restart */ storeApplicationData()235 private void storeApplicationData() { 236 SharedPreferences.Editor editor = 237 mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit(); 238 editor.putBoolean(SENDING_FLAG, mSendingFlag); 239 editor.putBoolean(MULTIPLE_FLAG, mMultipleFlag); 240 if (mMultipleFlag) { 241 editor.putString(MIME_TYPE_MULTIPLE, mMimeTypeOfSendingFiles); 242 StringBuilder sb = new StringBuilder(); 243 for (int i = 0, count = mUrisOfSendingFiles.size(); i < count; i++) { 244 Uri uriContent = mUrisOfSendingFiles.get(i); 245 sb.append(uriContent); 246 sb.append(ARRAYLIST_ITEM_SEPERATOR); 247 } 248 String strUris = sb.toString(); 249 editor.putString(FILE_URIS, strUris); 250 251 editor.remove(MIME_TYPE); 252 editor.remove(FILE_URI); 253 } else { 254 editor.putString(MIME_TYPE, mMimeTypeOfSendingFile); 255 editor.putString(FILE_URI, mUriOfSendingFile); 256 257 editor.remove(MIME_TYPE_MULTIPLE); 258 editor.remove(FILE_URIS); 259 } 260 editor.apply(); 261 Log.v(TAG, "Application data stored to SharedPreference! "); 262 } 263 saveSendingFileInfo( String mimeType, String uriString, boolean isHandover, boolean fromExternal)264 public void saveSendingFileInfo( 265 String mimeType, String uriString, boolean isHandover, boolean fromExternal) 266 throws IllegalArgumentException { 267 synchronized (BluetoothOppManager.this) { 268 mMultipleFlag = false; 269 mMimeTypeOfSendingFile = mimeType; 270 mIsHandoverInitiated = isHandover; 271 Uri uri = Uri.parse(uriString); 272 BluetoothOppSendFileInfo sendFileInfo = 273 BluetoothOppSendFileInfo.generateFileInfo( 274 mContext, uri, mimeType, fromExternal); 275 uri = BluetoothOppUtility.generateUri(uri, sendFileInfo); 276 BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo); 277 mUriOfSendingFile = uri.toString(); 278 storeApplicationData(); 279 } 280 } 281 saveSendingFileInfo( String mimeType, ArrayList<Uri> uris, boolean isHandover, boolean fromExternal)282 public void saveSendingFileInfo( 283 String mimeType, ArrayList<Uri> uris, boolean isHandover, boolean fromExternal) 284 throws IllegalArgumentException { 285 synchronized (BluetoothOppManager.this) { 286 mMultipleFlag = true; 287 mMimeTypeOfSendingFiles = mimeType; 288 mUrisOfSendingFiles = new ArrayList<Uri>(); 289 mIsHandoverInitiated = isHandover; 290 for (Uri uri : uris) { 291 BluetoothOppSendFileInfo sendFileInfo = 292 BluetoothOppSendFileInfo.generateFileInfo( 293 mContext, uri, mimeType, fromExternal); 294 uri = BluetoothOppUtility.generateUri(uri, sendFileInfo); 295 mUrisOfSendingFiles.add(uri); 296 BluetoothOppUtility.putSendFileInfo(uri, sendFileInfo); 297 } 298 storeApplicationData(); 299 } 300 } 301 302 /** 303 * Get the current status of Bluetooth hardware. 304 * 305 * @return true if Bluetooth enabled, false otherwise. 306 */ isEnabled()307 public boolean isEnabled() { 308 if (mAdapter != null) { 309 return BluetoothMethodProxy.getInstance().bluetoothAdapterIsEnabled(mAdapter); 310 } else { 311 Log.v(TAG, "BLUETOOTH_SERVICE is not available! "); 312 return false; 313 } 314 } 315 316 /** Enable Bluetooth hardware. */ enableBluetooth()317 public void enableBluetooth() { 318 if (mAdapter != null) { 319 mAdapter.enable(); 320 } 321 } 322 323 /** Disable Bluetooth hardware. */ disableBluetooth()324 public void disableBluetooth() { 325 if (mAdapter != null) { 326 mAdapter.disable(); 327 } 328 } 329 330 /** Get device name per bluetooth address. */ getDeviceName(BluetoothDevice device)331 public String getDeviceName(BluetoothDevice device) { 332 String deviceName = null; 333 334 if (device != null) { 335 deviceName = device.getAlias(); 336 if (deviceName == null) { 337 deviceName = BluetoothOppPreference.getInstance(mContext).getName(device); 338 } 339 } 340 341 if (deviceName == null) { 342 deviceName = mContext.getString(R.string.unknown_device); 343 } 344 345 return deviceName; 346 } 347 getBatchSize()348 public int getBatchSize() { 349 synchronized (BluetoothOppManager.this) { 350 return mFileNumInBatch; 351 } 352 } 353 354 /** Fork a thread to insert share info to db. */ startTransfer(BluetoothDevice device)355 public void startTransfer(BluetoothDevice device) { 356 Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum); 357 InsertShareInfoThread insertThread; 358 synchronized (BluetoothOppManager.this) { 359 if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) { 360 Log.e(TAG, "Too many shares user triggered concurrently!"); 361 ContentProfileErrorReportUtils.report( 362 BluetoothProfile.OPP, 363 BluetoothProtoEnums.BLUETOOTH_OPP_MANAGER, 364 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 365 0); 366 367 // Notice user 368 Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class); 369 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 370 in.putExtra("title", mContext.getString(R.string.enabling_progress_title)); 371 in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests)); 372 mContext.startActivity(in); 373 374 return; 375 } 376 insertThread = 377 new InsertShareInfoThread( 378 device, 379 mMultipleFlag, 380 mMimeTypeOfSendingFile, 381 mUriOfSendingFile, 382 mMimeTypeOfSendingFiles, 383 mUrisOfSendingFiles, 384 mIsHandoverInitiated); 385 if (mMultipleFlag) { 386 mFileNumInBatch = mUrisOfSendingFiles.size(); 387 } 388 } 389 390 insertThread.start(); 391 } 392 393 /** 394 * Thread to insert share info to db. In multiple files (say 100 files) share case, the 395 * inserting share info to db operation would be a time consuming operation, so need a thread to 396 * handle it. This thread allows multiple instances to support below case: User select multiple 397 * files to share to one device (say device 1), and then right away share to second device 398 * (device 2), we need insert all these share info to db. 399 */ 400 private class InsertShareInfoThread extends Thread { 401 private final BluetoothDevice mRemoteDevice; 402 403 private final String mTypeOfSingleFile; 404 405 private final String mUri; 406 407 private final String mTypeOfMultipleFiles; 408 409 private final ArrayList<Uri> mUris; 410 411 private final boolean mIsMultiple; 412 413 private final boolean mIsHandoverInitiated; 414 InsertShareInfoThread( BluetoothDevice device, boolean multiple, String typeOfSingleFile, String uri, String typeOfMultipleFiles, ArrayList<Uri> uris, boolean handoverInitiated)415 InsertShareInfoThread( 416 BluetoothDevice device, 417 boolean multiple, 418 String typeOfSingleFile, 419 String uri, 420 String typeOfMultipleFiles, 421 ArrayList<Uri> uris, 422 boolean handoverInitiated) { 423 super("Insert ShareInfo Thread"); 424 this.mRemoteDevice = device; 425 this.mIsMultiple = multiple; 426 this.mTypeOfSingleFile = typeOfSingleFile; 427 this.mUri = uri; 428 this.mTypeOfMultipleFiles = typeOfMultipleFiles; 429 this.mUris = uris; 430 this.mIsHandoverInitiated = handoverInitiated; 431 432 synchronized (BluetoothOppManager.this) { 433 mInsertShareThreadNum++; 434 } 435 436 Log.v(TAG, "Thread id is: " + this.getId()); 437 } 438 439 @Override run()440 public void run() { 441 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 442 if (mRemoteDevice == null) { 443 Log.e(TAG, "Target bt device is null!"); 444 ContentProfileErrorReportUtils.report( 445 BluetoothProfile.OPP, 446 BluetoothProtoEnums.BLUETOOTH_OPP_MANAGER, 447 BluetoothStatsLog.BLUETOOTH_CONTENT_PROFILE_ERROR_REPORTED__TYPE__LOG_ERROR, 448 1); 449 return; 450 } 451 if (mIsMultiple) { 452 insertMultipleShare(); 453 } else { 454 insertSingleShare(); 455 } 456 synchronized (BluetoothOppManager.this) { 457 mInsertShareThreadNum--; 458 } 459 } 460 461 /** Insert multiple sending sessions to db, only used by Opp application. */ insertMultipleShare()462 private void insertMultipleShare() { 463 int count = mUris.size(); 464 Long ts = System.currentTimeMillis(); 465 for (int i = 0; i < count; i++) { 466 Uri fileUri = mUris.get(i); 467 ContentValues values = new ContentValues(); 468 values.put(BluetoothShare.URI, fileUri.toString()); 469 ContentResolver contentResolver = mContext.getContentResolver(); 470 fileUri = BluetoothOppUtility.originalUri(fileUri); 471 String contentType = contentResolver.getType(fileUri); 472 Log.v(TAG, "Got mimetype: " + contentType + " Got uri: " + fileUri); 473 if (TextUtils.isEmpty(contentType)) { 474 contentType = mTypeOfMultipleFiles; 475 } 476 477 values.put(BluetoothShare.MIMETYPE, contentType); 478 values.put( 479 BluetoothShare.DESTINATION, 480 Flags.identityAddressNullIfUnknown() 481 ? Utils.getBrEdrAddress(mRemoteDevice) 482 : mRemoteDevice.getIdentityAddress()); 483 values.put(BluetoothShare.TIMESTAMP, ts); 484 if (mIsHandoverInitiated) { 485 values.put( 486 BluetoothShare.USER_CONFIRMATION, 487 BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED); 488 } 489 final Uri contentUri = 490 BluetoothMethodProxy.getInstance() 491 .contentResolverInsert( 492 mContext.getContentResolver(), 493 BluetoothShare.CONTENT_URI, 494 values); 495 Log.v( 496 TAG, 497 "Insert contentUri: " 498 + contentUri 499 + " to device: " 500 + getDeviceName(mRemoteDevice)); 501 } 502 } 503 504 /** Insert single sending session to db, only used by Opp application. */ insertSingleShare()505 private void insertSingleShare() { 506 ContentValues values = new ContentValues(); 507 values.put(BluetoothShare.URI, mUri); 508 values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile); 509 values.put( 510 BluetoothShare.DESTINATION, 511 Flags.identityAddressNullIfUnknown() 512 ? Utils.getBrEdrAddress(mRemoteDevice) 513 : mRemoteDevice.getIdentityAddress()); 514 if (mIsHandoverInitiated) { 515 values.put( 516 BluetoothShare.USER_CONFIRMATION, 517 BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED); 518 } 519 final Uri contentUri = 520 BluetoothMethodProxy.getInstance() 521 .contentResolverInsert( 522 mContext.getContentResolver(), 523 BluetoothShare.CONTENT_URI, 524 values); 525 Log.v( 526 TAG, 527 "Insert contentUri: " 528 + contentUri 529 + " to device: " 530 + getDeviceName(mRemoteDevice)); 531 } 532 } 533 cleanUpSendingFileInfo()534 void cleanUpSendingFileInfo() { 535 synchronized (BluetoothOppManager.this) { 536 Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag); 537 if (!mMultipleFlag && (mUriOfSendingFile != null)) { 538 Uri uri = Uri.parse(mUriOfSendingFile); 539 BluetoothOppUtility.closeSendFileInfo(uri); 540 } else if (mUrisOfSendingFiles != null) { 541 for (Uri uri : mUrisOfSendingFiles) { 542 BluetoothOppUtility.closeSendFileInfo(uri); 543 } 544 } 545 } 546 } 547 } 548