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