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.app.Activity;
38import android.app.AlertDialog;
39import android.bluetooth.BluetoothAdapter;
40import android.content.DialogInterface;
41import android.content.Intent;
42import android.content.ContentResolver;
43import android.database.Cursor;
44import android.net.Uri;
45import android.os.Bundle;
46import android.util.Log;
47import android.view.ContextMenu;
48import android.view.ContextMenu.ContextMenuInfo;
49import android.view.Menu;
50import android.view.MenuInflater;
51import android.view.MenuItem;
52import android.view.View;
53import android.widget.AdapterView;
54import android.widget.AdapterView.OnItemClickListener;
55import android.widget.ListView;
56
57/**
58 * View showing the user's finished bluetooth opp transfers that the user does
59 * not confirm. Including outbound and inbound transfers, both successful and
60 * failed. *
61 */
62public class BluetoothOppTransferHistory extends Activity implements
63        View.OnCreateContextMenuListener, OnItemClickListener {
64    private static final String TAG = "BluetoothOppTransferHistory";
65
66    private static final boolean V = Constants.VERBOSE;
67
68    private ListView mListView;
69
70    private Cursor mTransferCursor;
71
72    private BluetoothOppTransferAdapter mTransferAdapter;
73
74    private int mIdColumnId;
75
76    private int mContextMenuPosition;
77
78    private boolean mShowAllIncoming;
79
80    private boolean mContextMenu = false;
81
82    /** Class to handle Notification Manager updates */
83    private BluetoothOppNotification mNotifier;
84
85    @Override
86    public void onCreate(Bundle icicle) {
87        super.onCreate(icicle);
88        setContentView(R.layout.bluetooth_transfers_page);
89        mListView = (ListView)findViewById(R.id.list);
90        mListView.setEmptyView(findViewById(R.id.empty));
91
92        mShowAllIncoming = getIntent().getBooleanExtra(
93                Constants.EXTRA_SHOW_ALL_FILES, false);
94
95        String direction;
96        int dir = getIntent().getIntExtra("direction", 0);
97        if (dir == BluetoothShare.DIRECTION_OUTBOUND) {
98            setTitle(getText(R.string.outbound_history_title));
99            direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_OUTBOUND
100                    + ")";
101        } else {
102            if (mShowAllIncoming) {
103                setTitle(getText(R.string.btopp_live_folder));
104            } else {
105                setTitle(getText(R.string.inbound_history_title));
106            }
107            direction = "(" + BluetoothShare.DIRECTION + " == " + BluetoothShare.DIRECTION_INBOUND
108                    + ")";
109        }
110
111        String selection = BluetoothShare.STATUS + " >= '200' AND " + direction;
112
113        if (!mShowAllIncoming) {
114            selection = selection + " AND ("
115                    + BluetoothShare.VISIBILITY + " IS NULL OR "
116                    + BluetoothShare.VISIBILITY + " == '"
117                    + BluetoothShare.VISIBILITY_VISIBLE + "')";
118        }
119
120        final String sortOrder = BluetoothShare.TIMESTAMP + " DESC";
121
122        mTransferCursor = getContentResolver().query(BluetoothShare.CONTENT_URI,
123                new String[] {"_id", BluetoothShare.FILENAME_HINT, BluetoothShare.STATUS,
124                        BluetoothShare.TOTAL_BYTES, BluetoothShare._DATA, BluetoothShare.TIMESTAMP,
125                        BluetoothShare.VISIBILITY, BluetoothShare.DESTINATION,
126                        BluetoothShare.DIRECTION},
127                selection, null, sortOrder);
128
129        // only attach everything to the listbox if we can access
130        // the transfer database. Otherwise, just show it empty
131        if (mTransferCursor != null) {
132            mIdColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare._ID);
133            // Create a list "controller" for the data
134            mTransferAdapter = new BluetoothOppTransferAdapter(this,
135                    R.layout.bluetooth_transfer_item, mTransferCursor);
136            mListView.setAdapter(mTransferAdapter);
137            mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
138            mListView.setOnCreateContextMenuListener(this);
139            mListView.setOnItemClickListener(this);
140        }
141
142        mNotifier = new BluetoothOppNotification(this);
143        mContextMenu = false;
144    }
145
146    @Override
147    public boolean onCreateOptionsMenu(Menu menu) {
148        if (mTransferCursor != null && !mShowAllIncoming) {
149            MenuInflater inflater = getMenuInflater();
150            inflater.inflate(R.menu.transferhistory, menu);
151        }
152        return true;
153    }
154
155    @Override
156    public boolean onPrepareOptionsMenu(Menu menu) {
157        if (!mShowAllIncoming) {
158            boolean showClear = getClearableCount() > 0;
159            menu.findItem(R.id.transfer_menu_clear_all).setEnabled(showClear);
160        }
161        return super.onPrepareOptionsMenu(menu);
162    }
163
164    @Override
165    public boolean onOptionsItemSelected(MenuItem item) {
166        switch (item.getItemId()) {
167            case R.id.transfer_menu_clear_all:
168                promptClearList();
169                return true;
170        }
171        return false;
172    }
173
174    @Override
175    public boolean onContextItemSelected(MenuItem item) {
176        if (mTransferCursor.getCount() == 0) {
177            Log.i(TAG, "History is already cleared, not clearing again");
178            return true;
179        }
180        mTransferCursor.moveToPosition(mContextMenuPosition);
181        switch (item.getItemId()) {
182            case R.id.transfer_menu_open:
183                openCompleteTransfer();
184                updateNotificationWhenBtDisabled();
185                return true;
186
187            case R.id.transfer_menu_clear:
188                int sessionId = mTransferCursor.getInt(mIdColumnId);
189                Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
190                BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
191                updateNotificationWhenBtDisabled();
192                return true;
193        }
194        return false;
195    }
196
197    @Override
198    protected void onDestroy() {
199        if (mTransferCursor != null) {
200            mTransferCursor.close();
201        }
202        super.onDestroy();
203    }
204
205    @Override
206    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
207        if (mTransferCursor != null) {
208            mContextMenu = true;
209            AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo;
210            mTransferCursor.moveToPosition(info.position);
211            mContextMenuPosition = info.position;
212
213            String fileName = mTransferCursor.getString(mTransferCursor
214                    .getColumnIndexOrThrow(BluetoothShare.FILENAME_HINT));
215            if (fileName == null) {
216                fileName = this.getString(R.string.unknown_file);
217            }
218            menu.setHeaderTitle(fileName);
219
220            MenuInflater inflater = getMenuInflater();
221            if (mShowAllIncoming) {
222                inflater.inflate(R.menu.receivedfilescontextfinished, menu);
223            } else {
224                inflater.inflate(R.menu.transferhistorycontextfinished, menu);
225            }
226        }
227    }
228
229    /**
230     * Prompt the user if they would like to clear the transfer history
231     */
232    private void promptClearList() {
233        new AlertDialog.Builder(this).setTitle(R.string.transfer_clear_dlg_title).setMessage(
234                R.string.transfer_clear_dlg_msg).setPositiveButton(android.R.string.ok,
235                new DialogInterface.OnClickListener() {
236                    public void onClick(DialogInterface dialog, int whichButton) {
237                        clearAllDownloads();
238                    }
239                }).setNegativeButton(android.R.string.cancel, null).show();
240    }
241
242    /**
243     * Get the number of finished transfers, including error and success.
244     */
245    private int getClearableCount() {
246        int count = 0;
247        if (mTransferCursor.moveToFirst()) {
248            while (!mTransferCursor.isAfterLast()) {
249                int statusColumnId = mTransferCursor.getColumnIndexOrThrow(BluetoothShare.STATUS);
250                int status = mTransferCursor.getInt(statusColumnId);
251                if (BluetoothShare.isStatusCompleted(status)) {
252                    count++;
253                }
254                mTransferCursor.moveToNext();
255            }
256        }
257        return count;
258    }
259
260    /**
261     * Clear all finished transfers, error and success transfer items.
262     */
263    private void clearAllDownloads() {
264        if (mTransferCursor.moveToFirst()) {
265            while (!mTransferCursor.isAfterLast()) {
266                int sessionId = mTransferCursor.getInt(mIdColumnId);
267                Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
268                BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
269
270                mTransferCursor.moveToNext();
271            }
272            updateNotificationWhenBtDisabled();
273        }
274    }
275
276    /*
277     * (non-Javadoc)
278     * @see
279     * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget
280     * .AdapterView, android.view.View, int, long)
281     */
282    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
283        // Open the selected item
284        if (V) Log.v(TAG, "onItemClick: ContextMenu = " + mContextMenu);
285        if (!mContextMenu) {
286            mTransferCursor.moveToPosition(position);
287            openCompleteTransfer();
288            updateNotificationWhenBtDisabled();
289        }
290        mContextMenu = false;
291    }
292
293    /**
294     * Open the selected finished transfer. mDownloadCursor must be moved to
295     * appropriate position before calling this function
296     */
297    private void openCompleteTransfer() {
298        int sessionId = mTransferCursor.getInt(mIdColumnId);
299        Uri contentUri = Uri.parse(BluetoothShare.CONTENT_URI + "/" + sessionId);
300        BluetoothOppTransferInfo transInfo = BluetoothOppUtility.queryRecord(this, contentUri);
301        if (transInfo == null) {
302            Log.e(TAG, "Error: Can not get data from db");
303            return;
304        }
305        if (transInfo.mDirection == BluetoothShare.DIRECTION_INBOUND
306                && BluetoothShare.isStatusSuccess(transInfo.mStatus)) {
307            // if received file successfully, open this file
308            BluetoothOppUtility.updateVisibilityToHidden(this, contentUri);
309            BluetoothOppUtility.openReceivedFile(this, transInfo.mFileName, transInfo.mFileType,
310                    transInfo.mTimeStamp, contentUri);
311        } else {
312            Intent in = new Intent(this, BluetoothOppTransferActivity.class);
313            in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
314            in.setDataAndNormalize(contentUri);
315            this.startActivity(in);
316        }
317    }
318
319    /**
320     * When Bluetooth is disabled, notification can not be updated by
321     * ContentObserver in OppService, so need update manually.
322     */
323    private void updateNotificationWhenBtDisabled() {
324        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
325        if (!adapter.isEnabled()) {
326            if (V) Log.v(TAG, "Bluetooth is not enabled, update notification manually.");
327            mNotifier.updateNotification();
328        }
329    }
330}
331