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