blob: dd34c14acf78be93065f439d74ec85981569f883 [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 Scottc6bc1602009-03-24 20:42:08 -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;
39import android.view.Menu;
40import android.view.MenuInflater;
41import android.view.MenuItem;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.ContextMenu.ContextMenuInfo;
45import android.widget.AdapterView;
46import android.widget.ListView;
47
48/**
49 * View showing the user's bookmarks in the browser.
50 */
51public class BrowserBookmarksPage extends Activity implements
52 View.OnCreateContextMenuListener {
53
54 private BrowserBookmarksAdapter mBookmarksAdapter;
55 private static final int BOOKMARKS_SAVE = 1;
56 private boolean mMaxTabsOpen;
57 private BookmarkItem mContextHeader;
58 private AddNewBookmark mAddHeader;
59 private boolean mCanceled = false;
60 private boolean mCreateShortcut;
61 // XXX: There is no public string defining this intent so if Home changes
62 // the value, we have to update this string.
63 private static final String INSTALL_SHORTCUT =
64 "com.android.launcher.action.INSTALL_SHORTCUT";
65
66 private final static String LOGTAG = "browser";
67
68
69 @Override
70 public boolean onContextItemSelected(MenuItem item) {
71 // It is possible that the view has been canceled when we get to
72 // this point as back has a higher priority
73 if (mCanceled) {
74 return true;
75 }
76 AdapterView.AdapterContextMenuInfo i =
77 (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
78 // If we have no menu info, we can't tell which item was selected.
79 if (i == null) {
80 return true;
81 }
82
83 switch (item.getItemId()) {
84 case R.id.new_context_menu_id:
85 saveCurrentPage();
86 break;
87 case R.id.open_context_menu_id:
88 loadUrl(i.position);
89 break;
90 case R.id.edit_context_menu_id:
91 editBookmark(i.position);
92 break;
93 case R.id.shortcut_context_menu_id:
94 final Intent send = createShortcutIntent(getUrl(i.position),
Patrick Scottc6bc1602009-03-24 20:42:08 -070095 getBookmarkTitle(i.position), getFavicon(i.position));
The Android Open Source Project0c908882009-03-03 19:32:16 -080096 send.setAction(INSTALL_SHORTCUT);
97 sendBroadcast(send);
98 break;
99 case R.id.delete_context_menu_id:
100 displayRemoveBookmarkDialog(i.position);
101 break;
102 case R.id.new_window_context_menu_id:
103 openInNewWindow(i.position);
104 break;
105 case R.id.send_context_menu_id:
106 Browser.sendString(BrowserBookmarksPage.this, getUrl(i.position));
107 break;
108 case R.id.copy_url_context_menu_id:
109 copy(getUrl(i.position));
110
111 default:
112 return super.onContextItemSelected(item);
113 }
114 return true;
115 }
116
117 @Override
118 public void onCreateContextMenu(ContextMenu menu, View v,
119 ContextMenuInfo menuInfo) {
120 AdapterView.AdapterContextMenuInfo i =
121 (AdapterView.AdapterContextMenuInfo) menuInfo;
122
123 MenuInflater inflater = getMenuInflater();
124 inflater.inflate(R.menu.bookmarkscontext, menu);
125
126 if (0 == i.position) {
127 menu.setGroupVisible(R.id.CONTEXT_MENU, false);
128 if (mAddHeader == null) {
129 mAddHeader = new AddNewBookmark(BrowserBookmarksPage.this);
130 } else if (mAddHeader.getParent() != null) {
131 ((ViewGroup) mAddHeader.getParent()).
132 removeView(mAddHeader);
133 }
134 ((AddNewBookmark) i.targetView).copyTo(mAddHeader);
135 menu.setHeaderView(mAddHeader);
136 return;
137 }
138 menu.setGroupVisible(R.id.ADD_MENU, false);
139 BookmarkItem b = (BookmarkItem) i.targetView;
140 if (mContextHeader == null) {
141 mContextHeader = new BookmarkItem(BrowserBookmarksPage.this);
142 } else if (mContextHeader.getParent() != null) {
143 ((ViewGroup) mContextHeader.getParent()).
144 removeView(mContextHeader);
145 }
146 b.copyTo(mContextHeader);
147 menu.setHeaderView(mContextHeader);
148
149 if (mMaxTabsOpen) {
150 menu.findItem(R.id.new_window_context_menu_id).setVisible(
151 false);
152 }
153 }
154
155 /**
156 * Create a new BrowserBookmarksPage.
157 */
158 @Override
159 protected void onCreate(Bundle icicle) {
160 super.onCreate(icicle);
161
162 setContentView(R.layout.browser_bookmarks_page);
163 setTitle(R.string.browser_bookmarks_page_bookmarks_text);
164
165 if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
166 mCreateShortcut = true;
167 }
168
169 mBookmarksAdapter = new BrowserBookmarksAdapter(this,
170 getIntent().getStringExtra("url"), mCreateShortcut);
171 mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
172
173 ListView listView = (ListView) findViewById(R.id.list);
174 listView.setAdapter(mBookmarksAdapter);
175 listView.setDrawSelectorOnTop(false);
176 listView.setVerticalScrollBarEnabled(true);
177 listView.setOnItemClickListener(mListener);
178
179 if (!mCreateShortcut) {
180 listView.setOnCreateContextMenuListener(this);
181 }
182 }
183
184 private static final int SAVE_CURRENT_PAGE = 1000;
185 private final Handler mHandler = new Handler() {
186 @Override
187 public void handleMessage(Message msg) {
188 if (msg.what == SAVE_CURRENT_PAGE) {
189 saveCurrentPage();
190 }
191 }
192 };
193
194 private AdapterView.OnItemClickListener mListener = new AdapterView.OnItemClickListener() {
195 public void onItemClick(AdapterView parent, View v, int position, long id) {
196 // It is possible that the view has been canceled when we get to
197 // this point as back has a higher priority
198 if (mCanceled) {
199 android.util.Log.e("browser", "item clicked when dismising");
200 return;
201 }
202 if (!mCreateShortcut) {
203 if (0 == position) {
204 // XXX: Work-around for a framework issue.
205 mHandler.sendEmptyMessage(SAVE_CURRENT_PAGE);
206 } else {
207 loadUrl(position);
208 }
209 } else {
210 final Intent intent = createShortcutIntent(getUrl(position),
Patrick Scottc6bc1602009-03-24 20:42:08 -0700211 getBookmarkTitle(position), getFavicon(position));
The Android Open Source Project0c908882009-03-03 19:32:16 -0800212 setResultToParent(RESULT_OK, intent);
213 finish();
214 }
215 }
216 };
217
Patrick Scottc6bc1602009-03-24 20:42:08 -0700218 private Intent createShortcutIntent(String url, String title,
219 Bitmap favicon) {
The Android Open Source Project0c908882009-03-03 19:32:16 -0800220 final Intent i = new Intent();
The Android Open Source Projectf59ec872009-03-13 13:04:24 -0700221 final Intent shortcutIntent = new Intent(Intent.ACTION_VIEW,
222 Uri.parse(url));
223 long urlHash = url.hashCode();
224 long uniqueId = (urlHash << 32) | shortcutIntent.hashCode();
225 shortcutIntent.putExtra(Browser.EXTRA_APPLICATION_ID,
226 Long.toString(uniqueId));
227 i.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
The Android Open Source Project0c908882009-03-03 19:32:16 -0800228 i.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
Patrick Scottc6bc1602009-03-24 20:42:08 -0700229 if (favicon == null) {
230 i.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
231 Intent.ShortcutIconResource.fromContext(
232 BrowserBookmarksPage.this,
233 R.drawable.ic_launcher_shortcut_browser_bookmark));
234 } else {
235 Bitmap icon = BitmapFactory.decodeResource(getResources(),
236 R.drawable.ic_launcher_shortcut_browser_bookmark);
237
238 // Make a copy of the regular icon so we can modify the pixels.
239 Bitmap copy = icon.copy(Bitmap.Config.ARGB_8888, true);
240 Canvas canvas = new Canvas(copy);
241
242 // Make a Paint for the white background rectangle and for
243 // filtering the favicon.
244 Paint p = new Paint(Paint.ANTI_ALIAS_FLAG
245 | Paint.FILTER_BITMAP_FLAG);
246 p.setStyle(Paint.Style.FILL_AND_STROKE);
247 p.setColor(Color.WHITE);
248
249 // Create a rectangle that is slightly wider than the favicon
250 final float iconSize = 16; // 16x16 favicon
251 final float padding = 2; // white padding around icon
252 final float rectSize = iconSize + 2 * padding;
253 final float y = icon.getHeight() - rectSize;
254 RectF r = new RectF(0, y, rectSize, y + rectSize);
255
256 // Draw a white rounded rectangle behind the favicon
257 canvas.drawRoundRect(r, 2, 2, p);
258
259 // Draw the favicon in the same rectangle as the rounded rectangle
260 // but inset by the padding (results in a 16x16 favicon).
261 r.inset(padding, padding);
262 canvas.drawBitmap(favicon, null, r, p);
263 i.putExtra(Intent.EXTRA_SHORTCUT_ICON, copy);
264 }
The Android Open Source Project0c908882009-03-03 19:32:16 -0800265 // Do not allow duplicate items
266 i.putExtra("duplicate", false);
267 return i;
268 }
269
270 private void saveCurrentPage() {
271 Intent i = new Intent(BrowserBookmarksPage.this,
272 AddBookmarkPage.class);
273 i.putExtras(getIntent());
274 startActivityForResult(i, BOOKMARKS_SAVE);
275 }
276
277 private void loadUrl(int position) {
278 Intent intent = (new Intent()).setAction(getUrl(position));
279 setResultToParent(RESULT_OK, intent);
280 finish();
281 }
282
283 @Override
284 public boolean onCreateOptionsMenu(Menu menu) {
285 boolean result = super.onCreateOptionsMenu(menu);
286 if (!mCreateShortcut) {
287 MenuInflater inflater = getMenuInflater();
288 inflater.inflate(R.menu.bookmarks, menu);
289 return true;
290 }
291 return result;
292 }
293
294 @Override
295 public boolean onOptionsItemSelected(MenuItem item) {
296 switch (item.getItemId()) {
297 case R.id.new_context_menu_id:
298 saveCurrentPage();
299 break;
300
301 default:
302 return super.onOptionsItemSelected(item);
303 }
304 return true;
305 }
306
307 private void openInNewWindow(int position) {
308 Bundle b = new Bundle();
309 b.putBoolean("new_window", true);
310 setResultToParent(RESULT_OK,
311 (new Intent()).setAction(getUrl(position)).putExtras(b));
312
313 finish();
314 }
315
316
317 private void editBookmark(int position) {
318 Intent intent = new Intent(BrowserBookmarksPage.this,
319 AddBookmarkPage.class);
320 intent.putExtra("bookmark", getRow(position));
321 startActivityForResult(intent, BOOKMARKS_SAVE);
322 }
323
324 @Override
325 protected void onActivityResult(int requestCode, int resultCode,
326 Intent data) {
327 switch(requestCode) {
328 case BOOKMARKS_SAVE:
329 if (resultCode == RESULT_OK) {
330 Bundle extras;
331 if (data != null && (extras = data.getExtras()) != null) {
332 // If there are extras, then we need to save
333 // the edited bookmark. This is done in updateRow()
334 String title = extras.getString("title");
335 String url = extras.getString("url");
336 if (title != null && url != null) {
337 mBookmarksAdapter.updateRow(extras);
338 }
339 } else {
340 // extras == null then a new bookmark was added to
341 // the database.
342 refreshList();
343 }
344 }
345 break;
346 default:
347 break;
348 }
349 }
350
351 private void displayRemoveBookmarkDialog(int position) {
352 // Put up a dialog asking if the user really wants to
353 // delete the bookmark
354 final int deletePos = position;
355 new AlertDialog.Builder(this)
356 .setTitle(R.string.delete_bookmark)
357 .setIcon(android.R.drawable.ic_dialog_alert)
358 .setMessage(getText(R.string.delete_bookmark_warning).toString().replace(
359 "%s", getBookmarkTitle(deletePos)))
360 .setPositiveButton(R.string.ok,
361 new DialogInterface.OnClickListener() {
362 public void onClick(DialogInterface dialog, int whichButton) {
363 deleteBookmark(deletePos);
364 }
365 })
366 .setNegativeButton(R.string.cancel, null)
367 .show();
368 }
369
370 /**
371 * Refresh the shown list after the database has changed.
372 */
373 public void refreshList() {
374 mBookmarksAdapter.refreshList();
375 }
376
377 /**
378 * Return a hashmap representing the currently highlighted row.
379 */
380 public Bundle getRow(int position) {
381 return mBookmarksAdapter.getRow(position);
382 }
383
384 /**
385 * Return the url of the currently highlighted row.
386 */
387 public String getUrl(int position) {
388 return mBookmarksAdapter.getUrl(position);
389 }
390
Patrick Scottc6bc1602009-03-24 20:42:08 -0700391 /**
392 * Return the favicon of the currently highlighted row.
393 */
394 public Bitmap getFavicon(int position) {
395 return mBookmarksAdapter.getFavicon(position);
396 }
397
The Android Open Source Project0c908882009-03-03 19:32:16 -0800398 private void copy(CharSequence text) {
399 try {
400 IClipboard clip = IClipboard.Stub.asInterface(ServiceManager.getService("clipboard"));
401 if (clip != null) {
402 clip.setClipboardText(text);
403 }
404 } catch (android.os.RemoteException e) {
405 Log.e(LOGTAG, "Copy failed", e);
406 }
407 }
408
409 public String getBookmarkTitle(int position) {
410 return mBookmarksAdapter.getTitle(position);
411 }
412
413 /**
414 * Delete the currently highlighted row.
415 */
416 public void deleteBookmark(int position) {
417 mBookmarksAdapter.deleteRow(position);
418 }
419
420 public boolean dispatchKeyEvent(KeyEvent event) {
421 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isDown()) {
422 setResultToParent(RESULT_CANCELED, null);
423 mCanceled = true;
424 }
425 return super.dispatchKeyEvent(event);
426 }
427
428 // This Activity is generally a sub-Activity of CombinedHistoryActivity. In
429 // that situation, we need to pass our result code up to our parent.
430 // However, if someone calls this Activity directly, then this has no
431 // parent, and it needs to set it on itself.
432 private void setResultToParent(int resultCode, Intent data) {
433 Activity a = getParent() == null ? this : getParent();
434 a.setResult(resultCode, data);
435 }
436}