BluetoothOppManager.java revision 1bd017d12cf16ecd52fb486722e300790bddeefc
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.os.SystemClock;
47import android.text.TextUtils;
48import android.util.Log;
49import android.util.Pair;
50
51import java.util.ArrayList;
52import java.util.Iterator;
53import 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 */
60public class BluetoothOppManager {
61    private static final String TAG = "BluetoothOppManager";
62    private static final boolean V = Constants.VERBOSE;
63
64    private static BluetoothOppManager sInstance;
65
66    /** Used when obtaining a reference to the singleton instance. */
67    private static final 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 (sInstance == null) {
126                sInstance = new BluetoothOppManager();
127            }
128            sInstance.init(context);
129
130            return sInstance;
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 uriString, boolean isHandover,
250            boolean fromExternal) throws IllegalArgumentException {
251        synchronized (BluetoothOppManager.this) {
252            mMultipleFlag = false;
253            mMimeTypeOfSendingFile = mimeType;
254            mUriOfSendingFile = uriString;
255            mIsHandoverInitiated = isHandover;
256            Uri uri = Uri.parse(uriString);
257            BluetoothOppUtility.putSendFileInfo(
258                    uri, BluetoothOppSendFileInfo.generateFileInfo(
259                                 mContext, uri, mimeType, fromExternal));
260            storeApplicationData();
261        }
262    }
263
264    public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover,
265            boolean fromExternal) throws IllegalArgumentException {
266        synchronized (BluetoothOppManager.this) {
267            mMultipleFlag = true;
268            mMimeTypeOfSendingFiles = mimeType;
269            mUrisOfSendingFiles = uris;
270            mIsHandoverInitiated = isHandover;
271            for (Uri uri : uris) {
272                BluetoothOppUtility.putSendFileInfo(
273                        uri, BluetoothOppSendFileInfo.generateFileInfo(
274                                     mContext, uri, mimeType, fromExternal));
275            }
276            storeApplicationData();
277        }
278    }
279
280    /**
281     * Get the current status of Bluetooth hardware.
282     * @return true if Bluetooth enabled, false otherwise.
283     */
284    public boolean isEnabled() {
285        if (mAdapter != null) {
286            return mAdapter.isEnabled();
287        } else {
288            if (V) Log.v(TAG, "BLUETOOTH_SERVICE is not available! ");
289            return false;
290        }
291    }
292
293    /**
294     * Enable Bluetooth hardware.
295     */
296    public void enableBluetooth() {
297        if (mAdapter != null) {
298            mAdapter.enable();
299        }
300    }
301
302    /**
303     * Disable Bluetooth hardware.
304     */
305    public void disableBluetooth() {
306        if (mAdapter != null) {
307            mAdapter.disable();
308        }
309    }
310
311    /**
312     * Get device name per bluetooth address.
313     */
314    public String getDeviceName(BluetoothDevice device) {
315        String deviceName;
316
317        deviceName = BluetoothOppPreference.getInstance(mContext).getName(device);
318
319        if (deviceName == null && mAdapter != null) {
320            deviceName = device.getName();
321        }
322
323        if (deviceName == null) {
324            deviceName = mContext.getString(R.string.unknown_device);
325        }
326
327        return deviceName;
328    }
329
330    public int getBatchSize() {
331        synchronized (BluetoothOppManager.this) {
332            return mfileNumInBatch;
333        }
334    }
335
336    /**
337     * Fork a thread to insert share info to db.
338     */
339    public void startTransfer(BluetoothDevice device) {
340        if (V) Log.v(TAG, "Active InsertShareThread number is : " + mInsertShareThreadNum);
341        InsertShareInfoThread insertThread;
342        synchronized (BluetoothOppManager.this) {
343            if (mInsertShareThreadNum > ALLOWED_INSERT_SHARE_THREAD_NUMBER) {
344                Log.e(TAG, "Too many shares user triggered concurrently!");
345
346                // Notice user
347                Intent in = new Intent(mContext, BluetoothOppBtErrorActivity.class);
348                in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
349                in.putExtra("title", mContext.getString(R.string.enabling_progress_title));
350                in.putExtra("content", mContext.getString(R.string.ErrorTooManyRequests));
351                mContext.startActivity(in);
352
353                return;
354            }
355            insertThread = new InsertShareInfoThread(device, mMultipleFlag, mMimeTypeOfSendingFile,
356                    mUriOfSendingFile, mMimeTypeOfSendingFiles, mUrisOfSendingFiles,
357                    mIsHandoverInitiated);
358            if (mMultipleFlag) {
359                mfileNumInBatch = mUrisOfSendingFiles.size();
360            }
361        }
362
363        insertThread.start();
364    }
365
366    /**
367     * Thread to insert share info to db. In multiple files (say 100 files)
368     * share case, the inserting share info to db operation would be a time
369     * consuming operation, so need a thread to handle it. This thread allows
370     * multiple instances to support below case: User select multiple files to
371     * share to one device (say device 1), and then right away share to second
372     * device (device 2), we need insert all these share info to db.
373     */
374    private class InsertShareInfoThread extends Thread {
375        private final BluetoothDevice mRemoteDevice;
376
377        private final String mTypeOfSingleFile;
378
379        private final String mUri;
380
381        private final String mTypeOfMultipleFiles;
382
383        private final ArrayList<Uri> mUris;
384
385        private final boolean mIsMultiple;
386
387        private final boolean mIsHandoverInitiated;
388
389        InsertShareInfoThread(BluetoothDevice device, boolean multiple,
390                String typeOfSingleFile, String uri, String typeOfMultipleFiles,
391                ArrayList<Uri> uris, boolean handoverInitiated) {
392            super("Insert ShareInfo Thread");
393            this.mRemoteDevice = device;
394            this.mIsMultiple = multiple;
395            this.mTypeOfSingleFile = typeOfSingleFile;
396            this.mUri = uri;
397            this.mTypeOfMultipleFiles = typeOfMultipleFiles;
398            this.mUris = uris;
399            this.mIsHandoverInitiated = handoverInitiated;
400
401            synchronized (BluetoothOppManager.this) {
402                mInsertShareThreadNum++;
403            }
404
405            if (V) Log.v(TAG, "Thread id is: " + this.getId());
406        }
407
408        @Override
409        public void run() {
410            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
411            if (mRemoteDevice == null) {
412                Log.e(TAG, "Target bt device is null!");
413                return;
414            }
415            if (mIsMultiple) {
416                insertMultipleShare();
417            } else {
418                insertSingleShare();
419            }
420            synchronized (BluetoothOppManager.this) {
421                mInsertShareThreadNum--;
422            }
423        }
424
425        /**
426         * Insert multiple sending sessions to db, only used by Opp application.
427         */
428        private void insertMultipleShare() {
429            int count = mUris.size();
430            Long ts = System.currentTimeMillis();
431            for (int i = 0; i < count; i++) {
432                Uri fileUri = mUris.get(i);
433                ContentResolver contentResolver = mContext.getContentResolver();
434                String contentType = contentResolver.getType(fileUri);
435                if (V) Log.v(TAG, "Got mimetype: " + contentType + "  Got uri: " + fileUri);
436                if (TextUtils.isEmpty(contentType)) {
437                    contentType = mTypeOfMultipleFiles;
438                }
439
440                ContentValues values = new ContentValues();
441                values.put(BluetoothShare.URI, fileUri.toString());
442                values.put(BluetoothShare.MIMETYPE, contentType);
443                values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
444                values.put(BluetoothShare.TIMESTAMP, ts);
445                if (mIsHandoverInitiated) {
446                    values.put(BluetoothShare.USER_CONFIRMATION,
447                            BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
448                }
449                final Uri contentUri = mContext.getContentResolver().insert(
450                        BluetoothShare.CONTENT_URI, values);
451                if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: "
452                            + getDeviceName(mRemoteDevice));
453            }
454        }
455
456         /**
457         * Insert single sending session to db, only used by Opp application.
458         */
459        private void insertSingleShare() {
460            ContentValues values = new ContentValues();
461            values.put(BluetoothShare.URI, mUri);
462            values.put(BluetoothShare.MIMETYPE, mTypeOfSingleFile);
463            values.put(BluetoothShare.DESTINATION, mRemoteDevice.getAddress());
464            if (mIsHandoverInitiated) {
465                values.put(BluetoothShare.USER_CONFIRMATION,
466                        BluetoothShare.USER_CONFIRMATION_HANDOVER_CONFIRMED);
467            }
468            final Uri contentUri = mContext.getContentResolver().insert(BluetoothShare.CONTENT_URI,
469                    values);
470            if (V) Log.v(TAG, "Insert contentUri: " + contentUri + "  to device: "
471                                + getDeviceName(mRemoteDevice));
472        }
473    }
474
475    void cleanUpSendingFileInfo() {
476        synchronized (BluetoothOppManager.this) {
477            if (V) Log.v(TAG, "cleanUpSendingFileInfo: mMultipleFlag = " + mMultipleFlag);
478            if (!mMultipleFlag && (mUriOfSendingFile != null)) {
479                Uri uri = Uri.parse(mUriOfSendingFile);
480                BluetoothOppUtility.closeSendFileInfo(uri);
481            } else if (mUrisOfSendingFiles != null) {
482                for (Uri uri : mUrisOfSendingFiles) {
483                    BluetoothOppUtility.closeSendFileInfo(uri);
484                }
485            }
486        }
487    }
488}
489