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