BrowserBookmarksPage.java revision f59ec877363eaf43118677f249008eddc7a9ce11
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.content.DialogInterface;
22import android.content.Intent;
23import android.graphics.Bitmap;
24import android.net.Uri;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Message;
28import android.os.ServiceManager;
29import android.provider.Browser;
30import android.text.IClipboard;
31import android.util.Log;
32import android.view.ContextMenu;
33import android.view.KeyEvent;
34import android.view.Menu;
35import android.view.MenuInflater;
36import android.view.MenuItem;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.ContextMenu.ContextMenuInfo;
40import android.widget.AdapterView;
41import android.widget.ListView;
42
43/**
44 *  View showing the user's bookmarks in the browser.
45 */
46public class BrowserBookmarksPage extends Activity implements
47        View.OnCreateContextMenuListener {
48
49    private BrowserBookmarksAdapter mBookmarksAdapter;
50    private static final int        BOOKMARKS_SAVE = 1;
51    private boolean                 mMaxTabsOpen;
52    private BookmarkItem            mContextHeader;
53    private AddNewBookmark          mAddHeader;
54    private boolean                 mCanceled = false;
55    private boolean                 mCreateShortcut;
56    // XXX: There is no public string defining this intent so if Home changes
57    // the value, we have to update this string.
58    private static final String     INSTALL_SHORTCUT =
59            "com.android.launcher.action.INSTALL_SHORTCUT";
60
61    private final static String LOGTAG = "browser";
62
63
64    @Override
65    public boolean onContextItemSelected(MenuItem item) {
66        // It is possible that the view has been canceled when we get to
67        // this point as back has a higher priority
68        if (mCanceled) {
69            return true;
70        }
71        AdapterView.AdapterContextMenuInfo i =
72            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
73        // If we have no menu info, we can't tell which item was selected.
74        if (i == null) {
75            return true;
76        }
77
78        switch (item.getItemId()) {
79        case R.id.new_context_menu_id:
80            saveCurrentPage();
81            break;
82        case R.id.open_context_menu_id:
83            loadUrl(i.position);
84            break;
85        case R.id.edit_context_menu_id:
86            editBookmark(i.position);
87            break;
88        case R.id.shortcut_context_menu_id:
89            final Intent send = createShortcutIntent(getUrl(i.position),
90                    getBookmarkTitle(i.position));
91            send.setAction(INSTALL_SHORTCUT);
92            sendBroadcast(send);
93            break;
94        case R.id.delete_context_menu_id:
95            displayRemoveBookmarkDialog(i.position);
96            break;
97        case R.id.new_window_context_menu_id:
98            openInNewWindow(i.position);
99            break;
100        case R.id.send_context_menu_id:
101            Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position));
102            break;
103        case R.id.copy_url_context_menu_id:
104            copy(getUrl(i.position));
105
106        default:
107            return super.onContextItemSelected(item);
108        }
109        return true;
110    }
111
112    @Override
113    public void onCreateContextMenu(ContextMenu menu, View v,
114                ContextMenuInfo menuInfo) {
115            AdapterView.AdapterContextMenuInfo i =
116                    (AdapterView.AdapterContextMenuInfo) menuInfo;
117
118            MenuInflater inflater = getMenuInflater();
119            inflater.inflate(R.menu.bookmarkscontext, menu);
120
121            if (0 == i.position) {
122                menu.setGroupVisible(R.id.CONTEXT_MENU, false);
123                if (mAddHeader == null) {
124                    mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
125                } else if (mAddHeader.getParent() != null) {
126                    ((ViewGroup) mAddHeader.getParent()).
127                            removeView(mAddHeader);
128                }
129                ((AddNewBookmark) i.targetView).copyTo(mAddHeader);
130                menu.setHeaderView(mAddHeader);
131                return;
132            }
133            menu.setGroupVisible(R.id.ADD_MENU, false);
134            BookmarkItem b = (BookmarkItem) i.targetView;
135            if (mContextHeader == null) {
136                mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
137            } else if (mContextHeader.getParent() != null) {
138                ((ViewGroup) mContextHeader.getParent()).
139                        removeView(mContextHeader);
140            }
141            b.copyTo(mContextHeader);
142            menu.setHeaderView(mContextHeader);
143
144            if (mMaxTabsOpen) {
145                menu.findItem(R.id.new_window_context_menu_id).setVisible(
146                        false);
147            }
148        }
149
150    /**
151     *  Create a new BrowserBookmarksPage.
152     */
153    @Override
154    protected void onCreate(Bundle icicle) {
155        super.onCreate(icicle);
156
157        setContentView(R.layout.browser_bookmarks_page);
158        setTitle(R.string.browser_bookmarks_page_bookmarks_text);
159
160        if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
161            mCreateShortcut = true;
162        }
163
164        mBookmarksAdapter = new BrowserBookmarksAdapter(this,
165                getIntent().getStringExtra("url"), mCreateShortcut);
166        mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
167
168        ListView listView = (ListView) findViewById(R.id.list);
169        listView.setAdapter(mBookmarksAdapter);
170        listView.setDrawSelectorOnTop(false);
171        listView.setVerticalScrollBarEnabled(true);
172        listView.setOnItemClickListener(mListener);
173
174        if (!mCreateShortcut) {
175            listView.setOnCreateContextMenuListener(this);
176        }
177    }
178
179    private static final int SAVE_CURRENT_PAGE = 1000;
180    private final Handler mHandler = new Handler() {
181        @Override
182        public void handleMessage(Message msg) {
183            if (msg.what == SAVE_CURRENT_PAGE) {
184                saveCurrentPage();
185            }
186        }
187    };
188
189    private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
190        public void onItemClick(AdapterView parent, View v, int position, long id) {
191            // It is possible that the view has been canceled when we get to
192            // this point as back has a higher priority
193            if (mCanceled) {
194                android.util.Log.e("browser", "item clicked when dismising");
195                return;
196            }
197            if (!mCreateShortcut) {
198                if (0 == position) {
199                    // XXX: Work-around for a framework issue.
200                    mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
201                } else {
202                    loadUrl(position);
203                }
204            } else {
205                final Intent intent = createShortcutIntent(getUrl(position),
206                        getBookmarkTitle(position));
207                setResultToParent(RESULT_OK, intent);
208                finish();
209            }
210        }
211    };
212
213    private Intent createShortcutIntent(String url, String title) {
214        final Intent i = new Intent();
215        final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW,
216                Uri.parse(url));
217        long urlHash = url.hashCode();
218        long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
219        shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
220                Long.toString(uniqueId));
221        i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
222        i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
223        i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
224                Intent.ShortcutIconResource.fromContext(BrowserBookmarksPage.this,
225                        R.drawable.ic_launcher_browser));
226        // Do not allow duplicate items
227        i.putExtra("duplicate", false);
228        return i;
229    }
230
231    private void saveCurrentPage() {
232        Intent i = new Intent(BrowserBookmarksPage.this,
233                AddBookmarkPage.class);
234        i.putExtras(getIntent());
235        startActivityForResult(i, BOOKMARKS_SAVE);
236    }
237
238    private void loadUrl(int position) {
239        Intent intent = (new Intent()).setAction(getUrl(position));
240        setResultToParent(RESULT_OK, intent);
241        finish();
242    }
243
244    @Override
245    public boolean onCreateOptionsMenu(Menu menu) {
246        boolean result = super.onCreateOptionsMenu(menu);
247        if (!mCreateShortcut) {
248            MenuInflater inflater = getMenuInflater();
249            inflater.inflate(R.menu.bookmarks, menu);
250            return true;
251        }
252        return result;
253    }
254
255    @Override
256    public boolean onOptionsItemSelected(MenuItem item) {
257        switch (item.getItemId()) {
258            case R.id.new_context_menu_id:
259                saveCurrentPage();
260                break;
261
262            default:
263                return super.onOptionsItemSelected(item);
264        }
265        return true;
266    }
267
268    private void openInNewWindow(int position) {
269        Bundle b = new Bundle();
270        b.putBoolean("new_window", true);
271        setResultToParent(RESULT_OK,
272                (new Intent()).setAction(getUrl(position)).putExtras(b));
273
274        finish();
275    }
276
277
278    private void editBookmark(int position) {
279        Intent intent = new Intent(BrowserBookmarksPage.this,
280            AddBookmarkPage.class);
281        intent.putExtra("bookmark", getRow(position));
282        startActivityForResult(intent, BOOKMARKS_SAVE);
283    }
284
285    @Override
286    protected void onActivityResult(int requestCode, int resultCode,
287                                    Intent data) {
288        switch(requestCode) {
289            case BOOKMARKS_SAVE:
290                if (resultCode == RESULT_OK) {
291                    Bundle extras;
292                    if (data != null && (extras = data.getExtras()) != null) {
293                        // If there are extras, then we need to save
294                        // the edited bookmark. This is done in updateRow()
295                        String title = extras.getString("title");
296                        String url = extras.getString("url");
297                        if (title != null && url != null) {
298                            mBookmarksAdapter.updateRow(extras);
299                        }
300                    } else {
301                        // extras == null then a new bookmark was added to
302                        // the database.
303                        refreshList();
304                    }
305                }
306                break;
307            default:
308                break;
309        }
310    }
311
312    private void displayRemoveBookmarkDialog(int position) {
313        // Put up a dialog asking if the user really wants to
314        // delete the bookmark
315        final int deletePos = position;
316        new AlertDialog.Builder(this)
317                .setTitle(R.string.delete_bookmark)
318                .setIcon(android.R.drawable.ic_dialog_alert)
319                .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
320                        "%s", getBookmarkTitle(deletePos)))
321                .setPositiveButton(R.string.ok,
322                        new DialogInterface.OnClickListener() {
323                            public void onClick(DialogInterface dialog, int whichButton) {
324                                deleteBookmark(deletePos);
325                            }
326                        })
327                .setNegativeButton(R.string.cancel, null)
328                .show();
329    }
330
331    /**
332     *  Refresh the shown list after the database has changed.
333     */
334    public void refreshList() {
335        mBookmarksAdapter.refreshList();
336    }
337
338    /**
339     *  Return a hashmap representing the currently highlighted row.
340     */
341    public Bundle getRow(int position) {
342        return mBookmarksAdapter.getRow(position);
343    }
344
345    /**
346     *  Return the url of the currently highlighted row.
347     */
348    public String getUrl(int position) {
349        return mBookmarksAdapter.getUrl(position);
350    }
351
352    private void copy(CharSequence text) {
353        try {
354            IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
355            if (clip != null) {
356                clip.setClipboardText(text);
357            }
358        } catch (android.os.RemoteException e) {
359            Log.e(LOGTAG, "Copy failed", e);
360        }
361    }
362
363    public String getBookmarkTitle(int position) {
364        return mBookmarksAdapter.getTitle(position);
365    }
366
367    /**
368     *  Delete the currently highlighted row.
369     */
370    public void deleteBookmark(int position) {
371        mBookmarksAdapter.deleteRow(position);
372    }
373
374    public boolean dispatchKeyEvent(KeyEvent event) {
375        if (event.getKeyCode() ==  KeyEvent.KEYCODE_BACK && event.isDown()) {
376            setResultToParent(RESULT_CANCELED, null);
377            mCanceled = true;
378        }
379        return super.dispatchKeyEvent(event);
380    }
381
382    // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
383    // that situation, we need to pass our result code up to our parent.
384    // However, if someone calls this Activity directly, then this has no
385    // parent, and it needs to set it on itself.
386    private void setResultToParent(int resultCode, Intent data) {
387        Activity a = getParent() == null ? this : getParent();
388        a.setResult(resultCode, data);
389    }
390}
391