blob: 428aa92b09a5f4b6f72f4f5657b4eccef67165b7 [file] [log] [blame]
The Android Open Source Project0c908882009-03-03 19:32:16 -08001/*
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;
Patrick Scotte09761e2009-03-24 20:43:37 -070024import android.graphics.BitmapFactory;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.Paint;
28import android.graphics.RectF;
The Android Open Source Project0c908882009-03-03 19:32:16 -080029import android.net.Uri;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Message;
33import android.os.ServiceManager;
34import android.provider.Browser;
35import android.text.IClipboard;
36import android.util.Log;
37import android.view.ContextMenu;
38import android.view.KeyEvent;
Leon Scroggins892df312009-07-14 14:48:02 -040039import android.view.LayoutInflater;
The Android Open Source Project0c908882009-03-03 19:32:16 -080040import android.view.Menu;
41import android.view.MenuInflater;
42import android.view.MenuItem;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.ContextMenu.ContextMenuInfo;
46import android.widget.AdapterView;
Leon Scroggins89c6d362009-07-15 16:54:37 -040047import android.widget.GridView;
The Android Open Source Project0c908882009-03-03 19:32:16 -080048import android.widget.ListView;
Leon Scrogginsfeb941d2009-05-28 17:27:38 -040049import android.widget.Toast;
The Android Open Source Project0c908882009-03-03 19:32:16 -080050
51/**
52 * View showing the user's bookmarks in the browser.
53 */
54public class BrowserBookmarksPage extends Activity implements
55 View.OnCreateContextMenuListener {
56
Leon Scroggins892df312009-07-14 14:48:02 -040057 private boolean mGridMode;
Leon Scroggins89c6d362009-07-15 16:54:37 -040058 private GridView mGridPage;
Leon Scroggins892df312009-07-14 14:48:02 -040059 private View mVerticalList;
The Android Open Source Project0c908882009-03-03 19:32:16 -080060 private BrowserBookmarksAdapter mBookmarksAdapter;
61 private static final int BOOKMARKS_SAVE = 1;
62 private boolean mMaxTabsOpen;
63 private BookmarkItem mContextHeader;
64 private AddNewBookmark mAddHeader;
65 private boolean mCanceled = false;
66 private boolean mCreateShortcut;
67 // XXX: There is no public string defining this intent so if Home changes
68 // the value, we have to update this string.
69 private static final String INSTALL_SHORTCUT =
70 "com.android.launcher.action.INSTALL_SHORTCUT";
71
72 private final static String LOGTAG = "browser";
73
74
75 @Override
76 public boolean onContextItemSelected(MenuItem item) {
77 // It is possible that the view has been canceled when we get to
78 // this point as back has a higher priority
79 if (mCanceled) {
80 return true;
81 }
82 AdapterView.AdapterContextMenuInfo i =
83 (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
84 // If we have no menu info, we can't tell which item was selected.
85 if (i == null) {
86 return true;
87 }
88
89 switch (item.getItemId()) {
90 case R.id.new_context_menu_id:
91 saveCurrentPage();
92 break;
93 case R.id.open_context_menu_id:
94 loadUrl(i.position);
95 break;
96 case R.id.edit_context_menu_id:
97 editBookmark(i.position);
98 break;
99 case R.id.shortcut_context_menu_id:
100 final Intent send = createShortcutIntent(getUrl(i.position),
Patrick Scotte09761e2009-03-24 20:43:37 -0700101 getBookmarkTitle(i.position), getFavicon(i.position));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800102 send.setAction(INSTALL_SHORTCUT);
103 sendBroadcast(send);
104 break;
105 case R.id.delete_context_menu_id:
106 displayRemoveBookmarkDialog(i.position);
107 break;
108 case R.id.new_window_context_menu_id:
109 openInNewWindow(i.position);
110 break;
111 case R.id.send_context_menu_id:
112 Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position));
113 break;
114 case R.id.copy_url_context_menu_id:
115 copy(getUrl(i.position));
Leon Scrogginsfeb941d2009-05-28 17:27:38 -0400116 break;
117 case R.id.homepage_context_menu_id:
118 BrowserSettings.getInstance().setHomePage(this,
119 getUrl(i.position));
120 Toast.makeText(this, R.string.homepage_set,
121 Toast.LENGTH_LONG).show();
122 break;
The Android Open Source Project0c908882009-03-03 19:32:16 -0800123 default:
124 return super.onContextItemSelected(item);
125 }
126 return true;
127 }
128
129 @Override
130 public void onCreateContextMenu(ContextMenu menu, View v,
131 ContextMenuInfo menuInfo) {
132 AdapterView.AdapterContextMenuInfo i =
133 (AdapterView.AdapterContextMenuInfo) menuInfo;
134
135 MenuInflater inflater = getMenuInflater();
136 inflater.inflate(R.menu.bookmarkscontext, menu);
137
138 if (0 == i.position) {
139 menu.setGroupVisible(R.id.CONTEXT_MENU, false);
140 if (mAddHeader == null) {
141 mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
142 } else if (mAddHeader.getParent() != null) {
143 ((ViewGroup) mAddHeader.getParent()).
144 removeView(mAddHeader);
145 }
Leon Scroggins892df312009-07-14 14:48:02 -0400146 mAddHeader.setUrl(getIntent().getStringExtra("url"));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800147 menu.setHeaderView(mAddHeader);
148 return;
149 }
150 menu.setGroupVisible(R.id.ADD_MENU, false);
Leon Scroggins892df312009-07-14 14:48:02 -0400151 if (mMaxTabsOpen) {
152 menu.findItem(R.id.new_window_context_menu_id).setVisible(
153 false);
154 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800155 if (mContextHeader == null) {
156 mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
157 } else if (mContextHeader.getParent() != null) {
158 ((ViewGroup) mContextHeader.getParent()).
159 removeView(mContextHeader);
160 }
Leon Scroggins892df312009-07-14 14:48:02 -0400161 if (mGridMode) {
162 mBookmarksAdapter.populateBookmarkItem(mContextHeader,
163 i.position);
164 } else {
165 BookmarkItem b = (BookmarkItem) i.targetView;
166 b.copyTo(mContextHeader);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800167 }
Leon Scroggins892df312009-07-14 14:48:02 -0400168 menu.setHeaderView(mContextHeader);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800169 }
170
171 /**
172 * Create a new BrowserBookmarksPage.
173 */
174 @Override
175 protected void onCreate(Bundle icicle) {
176 super.onCreate(icicle);
177
The Android Open Source Project0c908882009-03-03 19:32:16 -0800178 if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
179 mCreateShortcut = true;
180 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800181 mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
182
Leon Scroggins892df312009-07-14 14:48:02 -0400183 setTitle(R.string.browser_bookmarks_page_bookmarks_text);
184 mBookmarksAdapter = new BrowserBookmarksAdapter(this,
Leon Scroggins89c6d362009-07-15 16:54:37 -0400185 getIntent().getStringExtra("url"),
186 getIntent().getStringExtra("title"), mCreateShortcut);
187 switchViewMode(true);
Leon Scroggins892df312009-07-14 14:48:02 -0400188 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800189
Leon Scroggins892df312009-07-14 14:48:02 -0400190 /**
191 * Set the ContentView to be either the grid of thumbnails or the vertical
192 * list. Pass true to set it to the grid.
193 */
194 private void switchViewMode(boolean gridMode) {
195 mGridMode = gridMode;
196 mBookmarksAdapter.switchViewMode(gridMode);
197 if (mGridMode) {
198 if (mGridPage == null) {
Leon Scroggins89c6d362009-07-15 16:54:37 -0400199 mGridPage = new GridView(this);
200 mGridPage.setAdapter(mBookmarksAdapter);
Leon Scroggins892df312009-07-14 14:48:02 -0400201 mGridPage.setOnItemClickListener(mListener);
Leon Scroggins89c6d362009-07-15 16:54:37 -0400202 mGridPage.setNumColumns(GridView.AUTO_FIT);
203 // Keep this in sync with bookmark_thumb and
204 // BrowserActivity.updateScreenshot
205 mGridPage.setColumnWidth(100);
206 mGridPage.setFocusable(true);
207 mGridPage.setFocusableInTouchMode(true);
208 mGridPage.setSelector(android.R.drawable.gallery_thumb);
209 mGridPage.setVerticalSpacing(10);
Leon Scroggins892df312009-07-14 14:48:02 -0400210 if (!mCreateShortcut) {
211 mGridPage.setOnCreateContextMenuListener(this);
212 }
213 }
214 setContentView(mGridPage);
215 } else {
216 if (null == mVerticalList) {
217 LayoutInflater factory = LayoutInflater.from(this);
218 mVerticalList = factory.inflate(R.layout.browser_bookmarks_page,
219 null);
220
221 ListView listView
222 = (ListView) mVerticalList.findViewById(R.id.list);
223 listView.setAdapter(mBookmarksAdapter);
224 listView.setDrawSelectorOnTop(false);
225 listView.setVerticalScrollBarEnabled(true);
226 listView.setOnItemClickListener(mListener);
227
228 if (!mCreateShortcut) {
229 listView.setOnCreateContextMenuListener(this);
230 }
231 }
232 setContentView(mVerticalList);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800233 }
234 }
235
236 private static final int SAVE_CURRENT_PAGE = 1000;
237 private final Handler mHandler = new Handler() {
238 @Override
239 public void handleMessage(Message msg) {
240 if (msg.what == SAVE_CURRENT_PAGE) {
241 saveCurrentPage();
242 }
243 }
244 };
245
246 private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
247 public void onItemClick(AdapterView parent, View v, int position, long id) {
248 // It is possible that the view has been canceled when we get to
249 // this point as back has a higher priority
250 if (mCanceled) {
Leon Scroggins892df312009-07-14 14:48:02 -0400251 android.util.Log.e(LOGTAG, "item clicked when dismissing");
The Android Open Source Project0c908882009-03-03 19:32:16 -0800252 return;
253 }
254 if (!mCreateShortcut) {
255 if (0 == position) {
256 // XXX: Work-around for a framework issue.
257 mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
258 } else {
259 loadUrl(position);
260 }
261 } else {
262 final Intent intent = createShortcutIntent(getUrl(position),
Patrick Scotte09761e2009-03-24 20:43:37 -0700263 getBookmarkTitle(position), getFavicon(position));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800264 setResultToParent(RESULT_OK, intent);
265 finish();
266 }
267 }
268 };
269
Patrick Scotte09761e2009-03-24 20:43:37 -0700270 private Intent createShortcutIntent(String url, String title,
271 Bitmap favicon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800272 final Intent i = new Intent();
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700273 final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW,
274 Uri.parse(url));
275 long urlHash = url.hashCode();
276 long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
277 shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
278 Long.toString(uniqueId));
279 i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800280 i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
Patrick Scotte09761e2009-03-24 20:43:37 -0700281 if (favicon == null) {
282 i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
283 Intent.ShortcutIconResource.fromContext(
284 BrowserBookmarksPage.this,
285 R.drawable.ic_launcher_shortcut_browser_bookmark));
286 } else {
287 Bitmap icon = BitmapFactory.decodeResource(getResources(),
288 R.drawable.ic_launcher_shortcut_browser_bookmark);
289
290 // Make a copy of the regular icon so we can modify the pixels.
291 Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
292 Canvas canvas = new Canvas(copy);
293
294 // Make a Paint for the white background rectangle and for
295 // filtering the favicon.
296 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG
297 | Paint.FILTER_BITMAP_FLAG);
298 p.setStyle(Paint.Style.FILL_AND_STROKE);
299 p.setColor(Color.WHITE);
300
301 // Create a rectangle that is slightly wider than the favicon
302 final float iconSize = 16; // 16x16 favicon
303 final float padding = 2; // white padding around icon
304 final float rectSize = iconSize + 2 * padding;
305 final float y = icon.getHeight() - rectSize;
306 RectF r = new RectF(0, y, rectSize, y + rectSize);
307
308 // Draw a white rounded rectangle behind the favicon
309 canvas.drawRoundRect(r, 2, 2, p);
310
311 // Draw the favicon in the same rectangle as the rounded rectangle
312 // but inset by the padding (results in a 16x16 favicon).
313 r.inset(padding, padding);
314 canvas.drawBitmap(favicon, null, r, p);
315 i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy);
316 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800317 // Do not allow duplicate items
318 i.putExtra("duplicate", false);
319 return i;
320 }
321
322 private void saveCurrentPage() {
323 Intent i = new Intent(BrowserBookmarksPage.this,
324 AddBookmarkPage.class);
325 i.putExtras(getIntent());
326 startActivityForResult(i, BOOKMARKS_SAVE);
327 }
328
329 private void loadUrl(int position) {
330 Intent intent = (new Intent()).setAction(getUrl(position));
331 setResultToParent(RESULT_OK, intent);
332 finish();
333 }
334
335 @Override
336 public boolean onCreateOptionsMenu(Menu menu) {
337 boolean result = super.onCreateOptionsMenu(menu);
338 if (!mCreateShortcut) {
339 MenuInflater inflater = getMenuInflater();
340 inflater.inflate(R.menu.bookmarks, menu);
341 return true;
342 }
343 return result;
344 }
345
346 @Override
347 public boolean onOptionsItemSelected(MenuItem item) {
348 switch (item.getItemId()) {
Leon Scroggins892df312009-07-14 14:48:02 -0400349 case R.id.new_context_menu_id:
350 saveCurrentPage();
351 break;
352
353 case R.id.switch_mode_menu_id:
354 switchViewMode(!mGridMode);
355 break;
356
357 default:
358 return super.onOptionsItemSelected(item);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800359 }
360 return true;
361 }
362
363 private void openInNewWindow(int position) {
364 Bundle b = new Bundle();
365 b.putBoolean("new_window", true);
366 setResultToParent(RESULT_OK,
367 (new Intent()).setAction(getUrl(position)).putExtras(b));
368
369 finish();
370 }
371
372
373 private void editBookmark(int position) {
374 Intent intent = new Intent(BrowserBookmarksPage.this,
375 AddBookmarkPage.class);
376 intent.putExtra("bookmark", getRow(position));
377 startActivityForResult(intent, BOOKMARKS_SAVE);
378 }
379
380 @Override
381 protected void onActivityResult(int requestCode, int resultCode,
382 Intent data) {
383 switch(requestCode) {
384 case BOOKMARKS_SAVE:
385 if (resultCode == RESULT_OK) {
386 Bundle extras;
387 if (data != null && (extras = data.getExtras()) != null) {
388 // If there are extras, then we need to save
389 // the edited bookmark. This is done in updateRow()
390 String title = extras.getString("title");
391 String url = extras.getString("url");
392 if (title != null && url != null) {
393 mBookmarksAdapter.updateRow(extras);
394 }
395 } else {
396 // extras == null then a new bookmark was added to
397 // the database.
398 refreshList();
399 }
400 }
401 break;
402 default:
403 break;
404 }
405 }
406
407 private void displayRemoveBookmarkDialog(int position) {
408 // Put up a dialog asking if the user really wants to
409 // delete the bookmark
410 final int deletePos = position;
411 new AlertDialog.Builder(this)
412 .setTitle(R.string.delete_bookmark)
413 .setIcon(android.R.drawable.ic_dialog_alert)
414 .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
415 "%s", getBookmarkTitle(deletePos)))
416 .setPositiveButton(R.string.ok,
417 new DialogInterface.OnClickListener() {
418 public void onClick(DialogInterface dialog, int whichButton) {
419 deleteBookmark(deletePos);
420 }
421 })
422 .setNegativeButton(R.string.cancel, null)
423 .show();
424 }
425
426 /**
427 * Refresh the shown list after the database has changed.
428 */
Leon Scroggins892df312009-07-14 14:48:02 -0400429 private void refreshList() {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800430 mBookmarksAdapter.refreshList();
431 }
432
433 /**
434 * Return a hashmap representing the currently highlighted row.
435 */
436 public Bundle getRow(int position) {
437 return mBookmarksAdapter.getRow(position);
438 }
439
440 /**
441 * Return the url of the currently highlighted row.
442 */
443 public String getUrl(int position) {
444 return mBookmarksAdapter.getUrl(position);
445 }
446
Patrick Scotte09761e2009-03-24 20:43:37 -0700447 /**
448 * Return the favicon of the currently highlighted row.
449 */
450 public Bitmap getFavicon(int position) {
451 return mBookmarksAdapter.getFavicon(position);
452 }
453
The Android Open Source Project0c908882009-03-03 19:32:16 -0800454 private void copy(CharSequence text) {
455 try {
456 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
457 if (clip != null) {
458 clip.setClipboardText(text);
459 }
460 } catch (android.os.RemoteException e) {
461 Log.e(LOGTAG, "Copy failed", e);
462 }
463 }
464
465 public String getBookmarkTitle(int position) {
466 return mBookmarksAdapter.getTitle(position);
467 }
468
469 /**
470 * Delete the currently highlighted row.
471 */
472 public void deleteBookmark(int position) {
473 mBookmarksAdapter.deleteRow(position);
474 }
475
476 public boolean dispatchKeyEvent(KeyEvent event) {
477 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isDown()) {
478 setResultToParent(RESULT_CANCELED, null);
479 mCanceled = true;
480 }
481 return super.dispatchKeyEvent(event);
482 }
483
484 // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
485 // that situation, we need to pass our result code up to our parent.
486 // However, if someone calls this Activity directly, then this has no
487 // parent, and it needs to set it on itself.
488 private void setResultToParent(int resultCode, Intent data) {
489 Activity a = getParent() == null ? this : getParent();
490 a.setResult(resultCode, data);
491 }
492}