BluetoothOppManager.java revision d6eaf19f39e163e16d22e29907edec402b30622e
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 33package com.android.bluetooth.opp; 34 35import com.android.bluetooth.R; 36 37import android.bluetooth.BluetoothAdapter; 38import android.bluetooth.BluetoothDevice; 39import android.content.ContentResolver; 40import android.content.ContentValues; 41import android.content.Context; 42import android.content.Intent; 43import android.content.SharedPreferences; 44import android.net.Uri; 45import android.os.Process; 46import android.text.TextUtils; 47import android.util.Log; 48 49import java.util.ArrayList; 50import java.util.List; 51 52/** 53 * This class provides a simplified interface on top of other Bluetooth service 54 * layer components; Also it handles some Opp application level variables. It's 55 * a singleton got from BluetoothOppManager.getInstance(context); 56 */ 57public class BluetoothOppManager { 58 private static final String TAG = "BluetoothOppManager"; 59 private static final boolean V = Constants.VERBOSE; 60 61 private static BluetoothOppManager INSTANCE; 62 63 /** Used when obtaining a reference to the singleton instance. */ 64 private static Object INSTANCE_LOCK = new Object(); 65 66 private boolean mInitialized; 67 68 private Context mContext; 69 70 private BluetoothAdapter mAdapter; 71 72 private String mMimeTypeOfSendingFile; 73 74 private String mUriOfSendingFile; 75 76 private String mMimeTypeOfSendingFiles; 77 78 private ArrayList<Uri> mUrisOfSendingFiles; 79 80 private static final String OPP_PREFERENCE_FILE = "OPPMGR"; 81 82 private static final String SENDING_FLAG = "SENDINGFLAG"; 83 84 private static final String MIME_TYPE = "MIMETYPE"; 85 86 private static final String FILE_URI = "FILE_URI"; 87 88 private static final String MIME_TYPE_MULTIPLE = "MIMETYPE_MULTIPLE"; 89 90 private static final String FILE_URIS = "FILE_URIS"; 91 92 private static final String MULTIPLE_FLAG = "MULTIPLE_FLAG"; 93 94 private static final String ARRAYLIST_ITEM_SEPERATOR = ";"; 95 96 private static final int ALLOWED_INSERT_SHARE_THREAD_NUMBER = 3; 97 98 // used to judge if need continue sending process after received a 99 // ENABLED_ACTION 100 public boolean mSendingFlag; 101 102 public boolean mMultipleFlag; 103 104 private int mfileNumInBatch; 105 106 private int mInsertShareThreadNum = 0; 107 108 /** 109 * Get singleton instance. 110 */ 111 public static BluetoothOppManager getInstance(Context context) { 112 synchronized (INSTANCE_LOCK) { 113 if (INSTANCE == null) { 114 INSTANCE = new BluetoothOppManager(); 115 } 116 INSTANCE.init(context); 117 118 return INSTANCE; 119 } 120 } 121 122 /** 123 * init 124 */ 125 private boolean init(Context context) { 126 if (mInitialized) 127 return true; 128 mInitialized = true; 129 130 mContext = context; 131 132 mAdapter = BluetoothAdapter.getDefaultAdapter(); 133 if (mAdapter == null) { 134 if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not started! "); 135 } 136 137 // Restore data from preference 138 restoreApplicationData(); 139 140 return true; 141 } 142 143 List<String> mWhitelist = new ArrayList<String>(); 144 145 public void addToWhitelist(String address) { 146 //TODO: timeout whitelist 147 mWhitelist.add(address); 148 } 149 150 public boolean isWhitelisted(String address) { 151 return mWhitelist.contains(address); 152 } 153 154 /** 155 * Restore data from preference 156 */ 157 private void restoreApplicationData() { 158 SharedPreferences settings = mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0); 159 160 // All member vars are not initialized till now 161 mSendingFlag = settings.getBoolean(SENDING_FLAG, false); 162 mMimeTypeOfSendingFile = settings.getString(MIME_TYPE, null); 163 mUriOfSendingFile = settings.getString(FILE_URI, null); 164 mMimeTypeOfSendingFiles = settings.getString(MIME_TYPE_MULTIPLE, null); 165 mMultipleFlag = settings.getBoolean(MULTIPLE_FLAG, false); 166 167 if (V) Log.v(TAG, "restoreApplicationData! " + mSendingFlag + mMultipleFlag 168 + mMimeTypeOfSendingFile + mUriOfSendingFile); 169 170 String strUris = settings.getString(FILE_URIS, null); 171 mUrisOfSendingFiles = new ArrayList<Uri>(); 172 if (strUris != null) { 173 String[] splitUri = strUris.split(ARRAYLIST_ITEM_SEPERATOR); 174 for (int i = 0; i < splitUri.length; i++) { 175 mUrisOfSendingFiles.add(Uri.parse(splitUri[i])); 176 if (V) Log.v(TAG, "Uri in batch: " + Uri.parse(splitUri[i])); 177 } 178 } 179 180 mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0).edit().clear().apply(); 181 } 182 183 /** 184 * Save application data to preference, need restore these data when service restart 185 */ 186 private void storeApplicationData() { 187 SharedPreferences.Editor editor = mContext.getSharedPreferences(OPP_PREFERENCE_FILE, 0) 188 .edit(); 189 editor.putBoolean(SENDING_FLAG, mSendingFlag); 190 editor.putBoolean(MULTIPLE_FLAG, mMultipleFlag); 191 if (mMultipleFlag) { 192 editor.putString(MIME_TYPE_MULTIPLE, mMimeTypeOfSendingFiles); 193 StringBuilder sb = new StringBuilder(); 194 for (int i = 0, count = mUrisOfSendingFiles.size(); i < count; i++) { 195 Uri uriContent = mUrisOfSendingFiles.get(i); 196 sb.append(uriContent); 197 sb.append(ARRAYLIST_ITEM_SEPERATOR); 198 } 199 String strUris = sb.toString(); 200 editor.putString(FILE_URIS, strUris); 201 202 editor.remove(MIME_TYPE); 203 editor.remove(FILE_URI); 204 } else { 205 editor.putString(MIME_TYPE, mMimeTypeOfSendingFile); 206 editor.putString(FILE_URI, mUriOfSendingFile); 207 208 editor.remove(MIME_TYPE_MULTIPLE); 209 editor.remove(FILE_URIS); 210 } 211 editor.apply(); 212 if (V) Log.v(TAG, "Application data stored to SharedPreference! "); 213 } 214 215 public void saveSendingFileInfo(String mimeType, String uri) { 216 synchronized (BluetoothOppManager.this) { 217 mMultipleFlag = false; 218 mMimeTypeOfSendingFile = mimeType; 219 mUriOfSendingFile = uri; 220 storeApplicationData(); 221 } 222 } 223 224 public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris) { 225 synchronized (BluetoothOppManager.this) { 226 mMultipleFlag = true; 227 mMimeTypeOfSendingFiles = mimeType; 228 mUrisOfSendingFiles = uris; 229 storeApplicationData(); 230 } 231 } 232 233 /** 234 * Get the current status of Bluetooth hardware. 235 * @return true if Bluetooth enabled, false otherwise. 236 */ 237 public boolean isEnabled() { 238 if (mAdapter != null) { 239 return mAdapter.isEnabled(); 240 } else { 241 if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not available! "); 242 return false; 243 } 244 } 245 246 /** 247 * Enable Bluetooth hardware. 248 */ 249 public void enableBluetooth() { 250 if (mAdapter != null) { 251 mAdapter.enable(); 252 } 253 } 254 255 /** 256 * Disable Bluetooth hardware. 257 */ 258 public void disableBluetooth() { 259 if (mAdapter != null) { 260 mAdapter.disable(); 261 } 262 } 263 264 /** 265 * Get device name per bluetooth address. 266 */ 267 public String getDeviceName(BluetoothDevice device) { 268 String deviceName; 269 270 deviceName = BluetoothOppPreference.getInstance(mContext).getName(device); 271 272 if (deviceName == null && mAdapter != null) { 273 deviceName = device.getName(); 274 } 275 276 if (deviceName == null) { 277 deviceName = mContext.getString(R.string.unknown_device); 278 } 279 280 return deviceName; 281 } 282 283 public int getBatchSize() { 284 synchronized (BluetoothOppManager.this) { 285 return mfileNumInBatch; 286 } 287 } 288 289 /** 290 * Fork a thread to insert share info to db. 291 */ 292 public void startTransfer(BluetoothDevice device) { 293 if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum); 294 InsertShareInfoThread insertThread; 295 synchronized (BluetoothOppManager.this) { 296 if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) { 297 Log.e(TAG, "Too many shares user triggered concurrently!"); 298 299 // Notice user 300 Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class); 301 in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 302 in.putExtra("title", mContext.getString(R.string.enabling_progress_title)); 303 in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests)); 304 mContext.startActivity(in); 305 306 return; 307 } 308 insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile, 309 mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles); 310 if (mMultipleFlag) { 311 mfileNumInBatch = mUrisOfSendingFiles.size(); 312 } 313 } 314 315 insertThread.start(); 316 } 317 318 /** 319 * Thread to insert share info to db. In multiple files (say 100 files) 320 * share case, the inserting share info to db operation would be a time 321 * consuming operation, so need a thread to handle it. This thread allows 322 * multiple instances to support below case: User select multiple files to 323 * share to one device (say device 1), and then right away share to second 324 * device (device 2), we need insert all these share info to db. 325 */ 326 private class InsertShareInfoThread extends Thread { 327 private final BluetoothDevice mRemoteDevice; 328 329 private final String mTypeOfSingleFile; 330 331 private final String mUri; 332 333 private final String mTypeOfMultipleFiles; 334 335 private final ArrayList<Uri> mUris; 336 337 private final boolean mIsMultiple; 338 339 public InsertShareInfoThread(BluetoothDevice device, boolean multiple, 340 String typeOfSingleFile, String uri, String typeOfMultipleFiles, ArrayList<Uri> uris) { 341 super("Insert ShareInfo Thread"); 342 this.mRemoteDevice = device; 343 this.mIsMultiple = multiple; 344 this.mTypeOfSingleFile = typeOfSingleFile; 345 this.mUri = uri; 346 this.mTypeOfMultipleFiles = typeOfMultipleFiles; 347 this.mUris = uris; 348 349 synchronized (BluetoothOppManager.this) { 350 mInsertShareThreadNum++; 351 } 352 353 if (V) Log.v(TAG, "Thread id is: " + this.getId()); 354 } 355 356 @Override 357 public void run() { 358 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 359 if (mRemoteDevice == null) { 360 Log.e(TAG, "Target bt device is null!"); 361 return; 362 } 363 if (mIsMultiple) { 364 insertMultipleShare(); 365 } else { 366 insertSingleShare(); 367 } 368 synchronized (BluetoothOppManager.this) { 369 mInsertShareThreadNum--; 370 } 371 } 372 373 /** 374 * Insert multiple sending sessions to db, only used by Opp application. 375 */ 376 private void insertMultipleShare() { 377 int count = mUris.size(); 378 Long ts = System.currentTimeMillis(); 379 for (int i = 0; i < count; i++) { 380 Uri fileUri = mUris.get(i); 381 ContentResolver contentResolver = mContext.getContentResolver(); 382 String contentType = contentResolver.getType(fileUri); 383 if (V) Log.v(TAG, "Got mimetype: " + contentType + " Got uri: " + fileUri); 384 if (TextUtils.isEmpty(contentType)) { 385 contentType = mTypeOfMultipleFiles; 386 } 387 388 ContentValues values = new ContentValues(); 389 values.put(BluetoothShare.URI, fileUri.toString()); 390 values.put(BluetoothShare.MIMETYPE, contentType); 391 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress()); 392 values.put(BluetoothShare.TIMESTAMP, ts); 393 394 final Uri contentUri = mContext.getContentResolver().insert( 395 BluetoothShare.CONTENT_URI, values); 396 if (V) Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " 397 + getDeviceName(mRemoteDevice)); 398 } 399 } 400 401 /** 402 * Insert single sending session to db, only used by Opp application. 403 */ 404 private void insertSingleShare() { 405 ContentValues values = new ContentValues(); 406 values.put(BluetoothShare.URI, mUri); 407 values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile); 408 values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress()); 409 410 final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI, 411 values); 412 if (V) Log.v(TAG, "Insert contentUri: " + contentUri + " to device: " 413 + getDeviceName(mRemoteDevice)); 414 } 415 } 416 417} 418