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