Browser: add to support my navigation feature
When launch browser, display my navigation page.
We show a gridview in navigation page, user can
select to add website items to navigation page,
delete/edit existing website items, when tap a
item, browser will load the saved website.
CRs-Fixed: 530979
Change-Id: I9618b8df15f651ccee09b80de8626bacfb6fda12
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0dc3ccc..dd0fb31 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -251,6 +251,13 @@
</intent-filter>
</activity>
+ <!-- add or edit my navigation activity -->
+ <activity android:name=".mynavigation.AddMyNavigationPage"
+ android:label="@string/my_navigation_page_title"
+ android:configChanges="orientation|keyboardHidden"
+ android:windowSoftInputMode="stateHidden">
+ </activity>
+
<!-- Makes .BrowserActivity the search target for any activity in Browser -->
<meta-data android:name="android.app.default_searchable" android:value=".BrowserActivity" />
@@ -299,6 +306,10 @@
android:authorities="com.android.browser.snapshots"
android:exported="false" />
+ <provider
+ android:name=".provider.MyNavigationProvider"
+ android:authorities="com.android.browser.mynavigation"
+ android:exported="false" />
</application>
</manifest>
diff --git a/res/layout/my_navigation_add_page.xml b/res/layout/my_navigation_add_page.xml
new file mode 100755
index 0000000..493a916
--- /dev/null
+++ b/res/layout/my_navigation_add_page.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (c) 2013, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of The Linux Foundation nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="horizontal"
+ android:paddingLeft="5dip"
+ android:paddingRight="5dip" >
+
+ <TextView
+ android:id="@+id/dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:text="@string/my_navigation_edit_label"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@android:color/white" />
+ </LinearLayout>
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1" >
+
+ <TableLayout
+ android:id="@+id/default_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="10dip"
+ android:paddingRight="10dip"
+ android:paddingTop="10dip"
+ android:shrinkColumns="1"
+ android:stretchColumns="1" >
+
+ <TableRow android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:id="@+id/titleText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="left"
+ android:paddingTop="10dip"
+ android:text="@string/my_navigation_name"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dip"
+ android:inputType="textCapSentences"
+ android:selectAllOnFocus="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </TableRow>
+
+ <TableRow
+ android:id="@+id/row_address"
+ android:layout_marginBottom="10dip" >
+
+ <TextView
+ android:id="@+id/addressText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="left"
+ android:text="@string/my_navigation_address"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/address"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="10dip"
+ android:hint="@string/http"
+ android:inputType="textUri"
+ android:selectAllOnFocus="true"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </TableRow>
+ </TableLayout>
+ </ScrollView>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <View
+ android:id="@+id/add_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:background="?android:attr/dividerVertical" />
+
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <Button
+ android:id="@+id/cancel"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:text="@string/do_not_save" />
+
+ <Button
+ android:id="@+id/OK"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:layout_weight="1"
+ android:maxLines="2"
+ android:text="@string/save" />
+ </LinearLayout>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/res/menu/browsercontext.xml b/res/menu/browsercontext.xml
old mode 100644
new mode 100755
index 8137a67..baf5b42
--- a/res/menu/browsercontext.xml
+++ b/res/menu/browsercontext.xml
@@ -59,5 +59,11 @@
<item android:id="@+id/select_text_menu_id"
android:title="@string/select_dot"/>
</group>
+ <group android:id="@+id/MY_NAVIGATION_MENU">
+ <item android:id="@+id/edit_my_navigation_context_menu_id"
+ android:title="@string/my_navigation_edit_label"/>
+ <item android:id="@+id/delete_my_navigation_context_menu_id"
+ android:title="@string/my_navigation_delete_label"/>
+ </group>
</menu>
diff --git a/res/raw/my_navigation.ktpl b/res/raw/my_navigation.ktpl
new file mode 100755
index 0000000..b21ab0b
--- /dev/null
+++ b/res/raw/my_navigation.ktpl
@@ -0,0 +1,85 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+<head>
+<title><%@ string/my_navigation_page_title %></title>
+<meta name="viewport" content="width=device-width; initial-scale=1.0; user-scalable=0" />
+
+<style type="text/css">
+
+* {
+ padding: 0;
+ margin: 0;
+}
+
+body {
+ text-align: center;
+ margin: 0px auto;
+ padding: 0 8px 0 8px;
+ max-width: <%@ dimen/mynav_max_width %>px;
+}
+
+h3 {
+ text-align: center;
+ margin: 5px 0 5px 0;
+}
+
+.thumbwrap li {
+ display: inline-block;
+ margin: 0 7px 12px 7px;
+ padding: 0;
+}
+
+@media all and (orientation:portrait) {
+.thumbwrap li {
+ width: <%@ dimen/mynav_item_width_portrait %>px;
+}
+}
+
+@media all and (orientation:landscape) {
+.thumbwrap li {
+ width: <%@ dimen/mynav_item_width %>px;
+}
+}
+
+.thumbwrap a {
+ display: block;
+ text-decoration: none;
+ color: #000;
+}
+
+.thumbwrap img {
+ border: <%@ dimen/mv_border_width %>px solid #e0e0e0;
+ border-radius: 0px;
+ width: 100%;
+}
+
+.thumbwrap .caption {
+ margin-top: 2px;
+ margin-left: 4px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: block;
+ font-size: .8em;
+ text-align: center;
+}
+
+</style>
+
+</head>
+<body>
+ <br>
+ <ul class="thumbwrap">
+ <%{ my_navigation %>
+ <li>
+ <br>
+ <a href="<%= url %>">
+ <img class="wrimg" src="<%= thumbnail %>" />
+ </a>
+ <span class="caption"><%= title %></span>
+ </li>
+ <%} my_navigation %>
+ </ul>
+</body>
+</html>
diff --git a/res/raw/my_navigation_add.png b/res/raw/my_navigation_add.png
new file mode 100755
index 0000000..e11c88b
--- /dev/null
+++ b/res/raw/my_navigation_add.png
Binary files differ
diff --git a/res/raw/my_navigation_thumbnail_default.png b/res/raw/my_navigation_thumbnail_default.png
new file mode 100755
index 0000000..3be27ff
--- /dev/null
+++ b/res/raw/my_navigation_thumbnail_default.png
Binary files differ
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7d0223c..341af97 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -400,6 +400,20 @@
<string name="accessibility_transition_navscreen" msgid="2734915619351519547">"标签页管理"</string>
<string name="accessibility_button_bookmarks_folder_up" msgid="9179389954714270505">"上一个文件夹"</string>
+ <!--Add for Navigation Feature -->
+ <string name="my_navigation_delete_label">删除我的导航</string>
+ <string name="my_navigation_edit_label">编辑我的导航</string>
+ <string name="my_navigation_add_label">添加我的导航</string>
+ <string name="my_navigation_add">添加</string>
+ <string name="my_navigation_delete_msg">删除此网站?</string>
+ <string name="my_navigation_name">名称</string>
+ <string name="my_navigation_address">地址</string>
+ <string name="website_needs_title">名称不能为空</string>
+ <string name="website_needs_url">地址不能为空</string>
+ <string name="my_navigation_cannot_save_url">该网址可能不正确,请重新输入</string>
+ <string name="my_navigation_duplicate_url">该网址已经存在,请重新输入</string>
+ <string name="my_navigation_page_title">我的导航</string>
+
<!-- Add for Carrier Feature.Content description for AV save/play prompt dialog -->
<string name="http_video_msg">请选择以下操作</string>
<string name="video_save">保存</string>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
old mode 100644
new mode 100755
index 5e3ec8b..3171305
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -21,6 +21,12 @@
<dimen name="max_tab_width">300dp</dimen>
<dimen name="bookmarkThumbnailWidth">90dip</dimen>
<dimen name="bookmarkThumbnailHeight">80dip</dimen>
+ <!--Add for Navigation Feature -->
+ <dimen name="myNavigationThumbnailWidth">90dip</dimen>
+ <dimen name="myNavigationThumbnailHeight">80dip</dimen>
+ <dimen name="mynav_item_width">80px</dimen>
+ <dimen name="mynav_item_width_portrait">80px</dimen>
+ <dimen name="mynav_max_width">480px</dimen>
<!-- Height determined by measuring the TableLayout in
browser_add_bookmark_content and matching that. -->
<dimen name="folder_selector_height">181dip</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
old mode 100755
new mode 100644
index dab4f51..0fe9599
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1013,6 +1013,19 @@
<!-- Content description for navigating up in the bookmark folder hierarchy [CHAR LIMIT=NONE] -->
<string name="accessibility_button_bookmarks_folder_up">Previous folder</string>
+ <!--Add for Navigation Feature -->
+ <string name="my_navigation_delete_label">Delete My Navigation</string>
+ <string name="my_navigation_edit_label">Edit My Navigation</string>
+ <string name="my_navigation_add_label">Add My Navigation</string>
+ <string name="my_navigation_add">add</string>
+ <string name="my_navigation_delete_msg">Delete this item?</string>
+ <string name="my_navigation_name">Name</string>
+ <string name="my_navigation_address">Address</string>
+ <string name="website_needs_title">"Website must have a name."</string>
+ <string name="website_needs_url">"Website must have a location."</string>
+ <string name="my_navigation_cannot_save_url">This URL cannot be website.</string>
+ <string name="my_navigation_duplicate_url">This URL has already existed.</string>
+ <string name="my_navigation_page_title">My Navigation</string>
<!-- Add for Carrier Feature.Content description for AV save/play prompt dialog -->
<string name="http_video_msg">Please choose a following operation</string>
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 729e79e..b1df847 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -38,11 +38,13 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.http.SslError;
+import android.net.WebAddress;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -91,6 +93,8 @@
import com.android.browser.UI.ComboViews;
import com.android.browser.provider.BrowserProvider2.Thumbnails;
import com.android.browser.provider.SnapshotProvider.Snapshots;
+import com.android.browser.mynavigation.AddMyNavigationPage;
+import com.android.browser.mynavigation.MyNavigationUtil;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -120,6 +124,8 @@
private static final String PROP_NETSWITCH = "persist.env.browser.netswitch";
private static final String INTENT_WIFI_SELECTION_DATA_CONNECTION =
"android.net.wifi.cmcc.WIFI_SELECTION_DATA_CONNECTION";
+ private static final String OFFLINE_PAGE =
+ "content://com.android.browser.mynavigation/websites";
// public message ids
public final static int LOAD_URL = 1001;
@@ -142,6 +148,7 @@
final static int FILE_SELECTED = 4;
final static int AUTOFILL_SETUP = 5;
final static int VOICE_RESULT = 6;
+ final static int MY_NAVIGATION = 7;
private final static int WAKELOCK_TIMEOUT = 5 * 60 * 1000; // 5 minutes
@@ -231,6 +238,8 @@
private boolean mBlockEvents;
private String mVoiceResult;
+ private boolean mUpdateMyNavThumbnail;
+ private String mUpdateMyNavThumbnailUrl;
public Controller(Activity browser) {
mActivity = browser;
@@ -323,10 +332,23 @@
// If the intent is ACTION_VIEW and data is not null, the Browser is
// invoked to view the content by another application. In this case,
// the tab will be close when exit.
- UrlData urlData = IntentHandler.getUrlDataFromIntent(intent);
+ UrlData urlData = null;
+ if (intent.getData() != null
+ && Intent.ACTION_VIEW.equals(intent.getAction())
+ && intent.getData().toString().startsWith("content://")) {
+ urlData = new UrlData(intent.getData().toString());
+ } else {
+ urlData = IntentHandler.getUrlDataFromIntent(intent);
+ }
+
Tab t = null;
if (urlData.isEmpty()) {
- t = openTabToHomePage();
+ if (SystemProperties.get("persist.env.c.browser.resource", "default").equals(
+ "cmcc")) {
+ t = openTab(OFFLINE_PAGE, false, true, true);
+ } else {
+ t = openTabToHomePage();
+ }
} else {
t = openTab(urlData);
}
@@ -1256,6 +1278,8 @@
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Tab t = getCurrentTab();
Uri uri = intent.getData();
+ mUpdateMyNavThumbnail = true;
+ mUpdateMyNavThumbnailUrl = uri.toString();
loadUrl(t, uri.toString());
} else if (intent.hasExtra(ComboViewActivity.EXTRA_OPEN_ALL)) {
String[] urls = intent.getStringArrayExtra(
@@ -1289,6 +1313,18 @@
}
}
break;
+
+ case MY_NAVIGATION:
+ if (intent == null || resultCode != Activity.RESULT_OK) {
+ break;
+ }
+
+ if (intent.getBooleanExtra("need_refresh", false) &&
+ getCurrentTopWebView() != null) {
+ getCurrentTopWebView().reload();
+ }
+ break;
+
default:
break;
}
@@ -1385,6 +1421,7 @@
// Show the correct menu group
final String extra = result.getExtra();
+ final String navigationUrl = MyNavigationUtil.getMyNavigationUrl(extra);
if (extra == null) return;
menu.setGroupVisible(R.id.PHONE_MENU,
type == WebView.HitTestResult.PHONE_TYPE);
@@ -1392,12 +1429,30 @@
type == WebView.HitTestResult.EMAIL_TYPE);
menu.setGroupVisible(R.id.GEO_MENU,
type == WebView.HitTestResult.GEO_TYPE);
- menu.setGroupVisible(R.id.IMAGE_MENU,
- type == WebView.HitTestResult.IMAGE_TYPE
- || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
- menu.setGroupVisible(R.id.ANCHOR_MENU,
- type == WebView.HitTestResult.SRC_ANCHOR_TYPE
- || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+
+ String itemUrl = null;
+ String url = webview.getOriginalUrl();
+ if (url != null && url.equalsIgnoreCase(MyNavigationUtil.MY_NAVIGATION)) {
+ itemUrl = Uri.decode(navigationUrl);
+ if (itemUrl != null && !MyNavigationUtil.isDefaultMyNavigation(itemUrl)) {
+ menu.setGroupVisible(R.id.MY_NAVIGATION_MENU,
+ type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ } else {
+ menu.setGroupVisible(R.id.MY_NAVIGATION_MENU, false);
+ }
+ menu.setGroupVisible(R.id.IMAGE_MENU, false);
+ menu.setGroupVisible(R.id.ANCHOR_MENU, false);
+ } else {
+ menu.setGroupVisible(R.id.MY_NAVIGATION_MENU, false);
+
+ menu.setGroupVisible(R.id.IMAGE_MENU,
+ type == WebView.HitTestResult.IMAGE_TYPE
+ || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ menu.setGroupVisible(R.id.ANCHOR_MENU,
+ type == WebView.HitTestResult.SRC_ANCHOR_TYPE
+ || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
+ }
+
boolean hitText = type == WebView.HitTestResult.SRC_ANCHOR_TYPE
|| type == WebView.HitTestResult.PHONE_TYPE
|| type == WebView.HitTestResult.EMAIL_TYPE
@@ -1487,6 +1542,43 @@
});
}
}
+ if (url != null && url.equalsIgnoreCase(MyNavigationUtil.MY_NAVIGATION)) {
+ menu.setHeaderTitle(navigationUrl);
+ menu.findItem(R.id.open_newtab_context_menu_id).setVisible(false);
+
+ if (itemUrl != null) {
+ if (!MyNavigationUtil.isDefaultMyNavigation(itemUrl)) {
+ menu.findItem(R.id.edit_my_navigation_context_menu_id)
+ .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ final Intent intent = new Intent(Controller.this
+ .getContext(),
+ AddMyNavigationPage.class);
+ Bundle bundle = new Bundle();
+ String url = Uri.decode(navigationUrl);
+ bundle.putBoolean("isAdding", false);
+ bundle.putString("url", url);
+ bundle.putString("name", getNameFromUrl(url));
+ intent.putExtra("websites", bundle);
+ mActivity.startActivityForResult(intent, MY_NAVIGATION);
+ return false;
+ }
+ });
+ menu.findItem(R.id.delete_my_navigation_context_menu_id)
+ .setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ showMyNavigationDeleteDialog(Uri.decode(navigationUrl));
+ return false;
+ }
+ });
+ }
+ } else {
+ Log.e(LOGTAG, "mynavigation onCreateContextMenu itemUrl is null!");
+ }
+ }
+
if (type == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
break;
}
@@ -1531,6 +1623,152 @@
mUi.onContextMenuCreated(menu);
}
+ public void startAddMyNavigation(String url) {
+ final Intent intent = new Intent(Controller.this.getContext(), AddMyNavigationPage.class);
+ Bundle bundle = new Bundle();
+ bundle.putBoolean("isAdding", true);
+ bundle.putString("url", url);
+ bundle.putString("name", getNameFromUrl(url));
+ intent.putExtra("websites", bundle);
+ mActivity.startActivityForResult(intent, MY_NAVIGATION);
+ }
+
+ private void showMyNavigationDeleteDialog(final String itemUrl) {
+ new AlertDialog.Builder(this.getContext())
+ .setTitle(R.string.my_navigation_delete_label)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(R.string.my_navigation_delete_msg)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ deleteMyNavigationItem(itemUrl);
+ }
+ })
+ .setNegativeButton(R.string.cancel, null)
+ .show();
+ }
+
+ private void deleteMyNavigationItem(final String itemUrl) {
+ ContentResolver cr = this.getContext().getContentResolver();
+ Cursor cursor = null;
+
+ try {
+ cursor = cr.query(MyNavigationUtil.MY_NAVIGATION_URI,
+ new String[] {
+ MyNavigationUtil.ID
+ }, "url = ?", new String[] {
+ itemUrl
+ }, null);
+ if (null != cursor && cursor.moveToFirst()) {
+ Uri uri = ContentUris.withAppendedId(MyNavigationUtil.MY_NAVIGATION_URI,
+ cursor.getLong(0));
+
+ ContentValues values = new ContentValues();
+ values.put(MyNavigationUtil.TITLE, "");
+ values.put(MyNavigationUtil.URL, "ae://" + cursor.getLong(0) + "add-fav");
+ values.put(MyNavigationUtil.WEBSITE, 0 + "");
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bitmap bm = BitmapFactory.decodeResource(this.getContext().getResources(),
+ R.raw.my_navigation_add);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ values.put(MyNavigationUtil.THUMBNAIL, os.toByteArray());
+ Log.d(LOGTAG, "deleteMyNavigationItem uri is : " + uri);
+ cr.update(uri, values, null, null);
+ } else {
+ Log.e(LOGTAG, "deleteMyNavigationItem the item does not exist!");
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "deleteMyNavigationItem", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+
+ if (getCurrentTopWebView() != null) {
+ getCurrentTopWebView().reload();
+ }
+ }
+
+ private String getNameFromUrl(String itemUrl) {
+ ContentResolver cr = this.getContext().getContentResolver();
+ Cursor cursor = null;
+ String name = null;
+
+ try {
+ cursor = cr.query(MyNavigationUtil.MY_NAVIGATION_URI,
+ new String[] {
+ MyNavigationUtil.TITLE
+ }, "url = ?", new String[] {
+ itemUrl
+ }, null);
+ if (null != cursor && cursor.moveToFirst()) {
+ name = cursor.getString(0);
+ } else {
+ Log.e(LOGTAG, "this item does not exist!");
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "getNameFromUrl", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ return name;
+ }
+
+ private void updateMyNavigationThumbnail(final String itemUrl, WebView webView) {
+ int width = mActivity.getResources().getDimensionPixelOffset(
+ R.dimen.myNavigationThumbnailWidth);
+ int height = mActivity.getResources().getDimensionPixelOffset(
+ R.dimen.myNavigationThumbnailHeight);
+
+ final Bitmap bm = createScreenshot(webView, width, height);
+
+ if (bm == null) {
+ Log.e(LOGTAG, "updateMyNavigationThumbnail bm is null!");
+ return;
+ }
+
+ final ContentResolver cr = mActivity.getContentResolver();
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... unused) {
+ ContentResolver cr = mActivity.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = cr.query(MyNavigationUtil.MY_NAVIGATION_URI,
+ new String[] {
+ MyNavigationUtil.ID
+ }, "url = ?", new String[] {
+ itemUrl
+ }, null);
+ if (null != cursor && cursor.moveToFirst()) {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+ ContentValues values = new ContentValues();
+ values.put(MyNavigationUtil.THUMBNAIL, os.toByteArray());
+ Uri uri = ContentUris.withAppendedId(MyNavigationUtil.MY_NAVIGATION_URI,
+ cursor.getLong(0));
+ Log.d(LOGTAG, "updateMyNavigationThumbnail uri is " + uri);
+ cr.update(uri, values, null, null);
+ os.close();
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "updateMyNavigationThumbnail", e);
+ } catch (IOException e) {
+ Log.e(LOGTAG, "updateMyNavigationThumbnail", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+ }.execute();
+ }
+
/**
* As the menu can be open when loading state changes
* we must manually update the state of the stop/reload menu
@@ -2217,10 +2455,16 @@
}
final String url = tab.getUrl();
final String originalUrl = view.getOriginalUrl();
+ final String thumbnailUrl = mUpdateMyNavThumbnailUrl;
if (TextUtils.isEmpty(url)) {
return;
}
+ //update My Navigation Thumbnails
+ boolean isMyNavigationUrl = MyNavigationUtil.isMyNavigationUrl(mActivity, url);
+ if (isMyNavigationUrl) {
+ updateMyNavigationThumbnail(url, view);
+ }
// Only update thumbnails for web urls (http(s)://), not for
// about:, javascript:, data:, etc...
// Unless it is a bookmarked site, then always update
@@ -2228,6 +2472,24 @@
return;
}
+ if (url != null && mUpdateMyNavThumbnailUrl != null
+ && Patterns.WEB_URL.matcher(url).matches()
+ && Patterns.WEB_URL.matcher(mUpdateMyNavThumbnailUrl).matches()) {
+ String urlHost = (new WebAddress(url)).getHost();
+ String bookmarkHost = (new WebAddress(mUpdateMyNavThumbnailUrl)).getHost();
+ if (urlHost == null || urlHost.length() == 0 || bookmarkHost == null
+ || bookmarkHost.length() == 0) {
+ return;
+ }
+ String urlDomain = urlHost.substring(urlHost.indexOf('.'), urlHost.length());
+ String bookmarkDomain = bookmarkHost.substring(bookmarkHost.indexOf('.'),
+ bookmarkHost.length());
+ Log.d(LOGTAG, "addressUrl domain is " + urlDomain);
+ Log.d(LOGTAG, "bookmarkUrl domain is " + bookmarkDomain);
+ if (!bookmarkDomain.equals(urlDomain)) {
+ return;
+ }
+ }
final Bitmap bm = createScreenshot(view, getDesiredThumbnailWidth(mActivity),
getDesiredThumbnailHeight(mActivity));
if (bm == null) {
@@ -2241,7 +2503,13 @@
Cursor cursor = null;
try {
// TODO: Clean this up
- cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+ cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl,
+ mUpdateMyNavThumbnail ? ((thumbnailUrl != null) ? thumbnailUrl : url)
+ : url);
+ if (mUpdateMyNavThumbnail) {
+ mUpdateMyNavThumbnail = false;
+ mUpdateMyNavThumbnailUrl = null;
+ }
if (cursor != null && cursor.moveToFirst()) {
final ByteArrayOutputStream os =
new ByteArrayOutputStream();
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index e3a0333..852aa25 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -70,6 +70,8 @@
import com.android.browser.TabControl.OnThumbnailUpdatedListener;
import com.android.browser.homepages.HomeProvider;
+import com.android.browser.mynavigation.MyNavigationUtil;
+import com.android.browser.provider.MyNavigationProvider;
import com.android.browser.provider.SnapshotProvider.Snapshots;
import java.io.ByteArrayOutputStream;
@@ -626,8 +628,12 @@
@Override
public WebResourceResponse shouldInterceptRequest(WebView view,
String url) {
- WebResourceResponse res = HomeProvider.shouldInterceptRequest(
- mContext, url);
+ WebResourceResponse res;
+ if (MyNavigationUtil.MY_NAVIGATION.equals(url)) {
+ res = MyNavigationProvider.shouldInterceptRequest(mContext, url);
+ } else {
+ res = HomeProvider.shouldInterceptRequest(mContext, url);
+ }
return res;
}
diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java
old mode 100644
new mode 100755
index 37e70f8..ac8442a
--- a/src/com/android/browser/UrlHandler.java
+++ b/src/com/android/browser/UrlHandler.java
@@ -105,6 +105,10 @@
return false;
}
+ if (url.startsWith("ae://") && url.endsWith("add-fav")) {
+ mController.startAddMyNavigation(url);
+ return true;
+ }
// If this is a Google search, attempt to add an RLZ string
// (if one isn't already present).
if (rlzProviderPresent()) {
diff --git a/src/com/android/browser/UrlUtils.java b/src/com/android/browser/UrlUtils.java
old mode 100644
new mode 100755
index e24000c..ff78647
--- a/src/com/android/browser/UrlUtils.java
+++ b/src/com/android/browser/UrlUtils.java
@@ -130,7 +130,7 @@
return null;
}
- /* package */ static String fixUrl(String inUrl) {
+ public static String fixUrl(String inUrl) {
// FIXME: Converting the url to lower case
// duplicates functionality in smartUrlFilter().
// However, changing all current callers of fixUrl to
diff --git a/src/com/android/browser/mynavigation/AddMyNavigationPage.java b/src/com/android/browser/mynavigation/AddMyNavigationPage.java
new file mode 100755
index 0000000..1db9fa5
--- /dev/null
+++ b/src/com/android/browser/mynavigation/AddMyNavigationPage.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser.mynavigation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.ParseException;
+import android.net.Uri;
+import android.net.WebAddress;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.util.Log;
+
+import com.android.browser.BrowserUtils;
+import com.android.browser.R;
+import com.android.browser.UrlUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class AddMyNavigationPage extends Activity {
+
+ private static final String LOGTAG = "AddMyNavigationPage";
+ private static final int SAVE_SITE_NAVIGATION = 100;
+
+ private EditText mName;
+ private EditText mAddress;
+ private Button mButtonOK;
+ private Button mButtonCancel;
+ private Bundle mMap;
+ private String mItemUrl;
+ private boolean mIsAdding;
+ private TextView mDialogText;
+ private Handler mHandler;
+
+ private View.OnClickListener mOKListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ if (save()) {
+ AddMyNavigationPage.this.setResult(Activity.RESULT_OK,
+ (new Intent()).putExtra("need_refresh", true));
+ finish();
+ }
+ }
+ };
+
+ private View.OnClickListener mCancelListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ };
+
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.my_navigation_add_page);
+ String name = null;
+ String url = null;
+ mMap = getIntent().getExtras();
+ if (mMap != null) {
+ Bundle b = mMap.getBundle("websites");
+ if (b != null) {
+ mMap = b;
+ }
+ name = mMap.getString("name");
+ url = mMap.getString("url");
+ mIsAdding = mMap.getBoolean("isAdding");
+ }
+
+ // The original url
+ mItemUrl = url;
+ mName = (EditText) findViewById(R.id.title);
+ mAddress = (EditText) findViewById(R.id.address);
+
+ BrowserUtils.maxLengthFilter(AddMyNavigationPage.this, mName,
+ BrowserUtils.FILENAME_MAX_LENGTH);
+ BrowserUtils.maxLengthFilter(AddMyNavigationPage.this, mAddress,
+ BrowserUtils.ADDRESS_MAX_LENGTH);
+
+ if (url.startsWith("ae://") && url.endsWith("add-fav")) {
+ mName.setText("");
+ mAddress.setText("");
+ } else {
+ mName.setText(name);
+ mAddress.setText(url);
+ }
+ mDialogText = (TextView) findViewById(R.id.dialog_title);
+ if (mIsAdding) {
+ mDialogText.setText(R.string.my_navigation_add_label);
+ }
+
+ mButtonOK = (Button) findViewById(R.id.OK);
+ mButtonOK.setOnClickListener(mOKListener);
+ mButtonCancel = (Button) findViewById(R.id.cancel);
+ mButtonCancel.setOnClickListener(mCancelListener);
+
+ if (!getWindow().getDecorView().isInTouchMode()) {
+ mButtonOK.requestFocus();
+ }
+ }
+
+ /**
+ * Runnable to save a website
+ */
+ private class SaveMyNavigationRunnable implements Runnable {
+ private Message mMessage;
+
+ public SaveMyNavigationRunnable(Message msg) {
+ mMessage = msg;
+ }
+
+ public void run() {
+ Bundle bundle = mMessage.getData();
+ String title = bundle.getString("title");
+ String url = bundle.getString("url");
+ String itemUrl = bundle.getString("itemUrl");
+ Boolean toDefaultThumbnail = bundle.getBoolean("toDefaultThumbnail");
+ ContentResolver cr = AddMyNavigationPage.this.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = cr.query(MyNavigationUtil.MY_NAVIGATION_URI,
+ new String[] {
+ MyNavigationUtil.ID
+ }, "url = ?", new String[] {
+ itemUrl
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ ContentValues values = new ContentValues();
+ values.put(MyNavigationUtil.TITLE, title);
+ values.put(MyNavigationUtil.URL, url);
+ values.put(MyNavigationUtil.WEBSITE, 1 + "");
+ if (toDefaultThumbnail) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bitmap bm = BitmapFactory.decodeResource(
+ AddMyNavigationPage.this.getResources(),
+ R.raw.my_navigation_thumbnail_default);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ values.put(MyNavigationUtil.THUMBNAIL, os.toByteArray());
+ }
+ Uri uri = ContentUris.withAppendedId(MyNavigationUtil.MY_NAVIGATION_URI,
+ cursor.getLong(0));
+ cr.update(uri, values, null, null);
+ } else {
+ Log.e(LOGTAG, "this item does not exist!");
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "SaveMyNavigationRunnable", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ }
+ }
+
+ boolean save() {
+ String name = mName.getText().toString().trim();
+ String unfilteredUrl = UrlUtils.fixUrl(mAddress.getText().toString());
+ boolean emptyTitle = name.length() == 0;
+ boolean emptyUrl = unfilteredUrl.trim().length() == 0;
+ Resources r = getResources();
+ if (emptyTitle || emptyUrl) {
+ if (emptyTitle) {
+ mName.setError(r.getText(R.string.website_needs_title));
+ }
+ if (emptyUrl) {
+ mAddress.setError(r.getText(R.string.website_needs_url));
+ }
+ return false;
+ }
+ String url = unfilteredUrl.trim();
+ try {
+ if (!url.toLowerCase().startsWith("javascript:")) {
+ URI uriObj = new URI(url);
+ String scheme = uriObj.getScheme();
+ if (!MyNavigationUtil.urlHasAcceptableScheme(url)) {
+ if (scheme != null) {
+ mAddress.setError(r.getText(R.string.my_navigation_cannot_save_url));
+ return false;
+ }
+ WebAddress address;
+ try {
+ address = new WebAddress(unfilteredUrl);
+ } catch (ParseException e) {
+ throw new URISyntaxException("", "");
+ }
+ if (address.getHost().length() == 0) {
+ throw new URISyntaxException("", "");
+ }
+ url = address.toString();
+ } else {
+ String mark = "://";
+ int iRet = -1;
+ if (null != url) {
+ iRet = url.indexOf(mark);
+ }
+ if (iRet > 0 && url.indexOf("/", iRet + mark.length()) < 0) {
+ url = url + "/";
+ Log.d(LOGTAG, "URL=" + url);
+ }
+ }
+ }
+ } catch (URISyntaxException e) {
+ mAddress.setError(r.getText(R.string.bookmark_url_not_valid));
+ return false;
+ }
+
+ // When it is adding, avoid duplicate url that already existing in the
+ // database
+ if (!mItemUrl.equals(url)) {
+ boolean exist = MyNavigationUtil.isMyNavigationUrl(this, url);
+ if (exist) {
+ mAddress.setError(r.getText(R.string.my_navigation_duplicate_url));
+ return false;
+ }
+ }
+ Bundle bundle = new Bundle();
+ bundle.putString("title", name);
+ bundle.putString("url", url);
+ bundle.putString("itemUrl", mItemUrl);
+ if (!mItemUrl.equals(url)) {
+ bundle.putBoolean("toDefaultThumbnail", true);
+ } else {
+ bundle.putBoolean("toDefaultThumbnail", false);
+ }
+ Message msg = Message.obtain(mHandler, SAVE_SITE_NAVIGATION);
+ msg.setData(bundle);
+ Thread t = new Thread(new SaveMyNavigationRunnable(msg));
+ t.start();
+ return true;
+ }
+}
diff --git a/src/com/android/browser/mynavigation/MyNavigationRequestHandler.java b/src/com/android/browser/mynavigation/MyNavigationRequestHandler.java
new file mode 100755
index 0000000..e1f9ebe
--- /dev/null
+++ b/src/com/android/browser/mynavigation/MyNavigationRequestHandler.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser.mynavigation;
+
+import android.content.Context;
+import android.content.UriMatcher;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.android.browser.R;
+
+public class MyNavigationRequestHandler extends Thread {
+
+ private static final String LOGTAG = "MyNavigationRequestHandler";
+ private static final int MY_NAVIGATION = 1;
+ private static final int RESOURCE = 2;
+ private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+ Uri mUri;
+ Context mContext;
+ OutputStream mOutput;
+
+ static {
+ URI_MATCHER.addURI(MyNavigationUtil.AUTHORITY, "websites/res/*/*", RESOURCE);
+ URI_MATCHER.addURI(MyNavigationUtil.AUTHORITY, "websites", MY_NAVIGATION);
+ }
+
+ public MyNavigationRequestHandler(Context context, Uri uri, OutputStream out) {
+ mUri = uri;
+ mContext = context.getApplicationContext();
+ mOutput = out;
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ try {
+ doHandleRequest();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Failed to handle request: " + mUri, e);
+ } finally {
+ cleanup();
+ }
+ }
+
+ void doHandleRequest() throws IOException {
+ int match = URI_MATCHER.match(mUri);
+ switch (match) {
+ case MY_NAVIGATION:
+ writeTemplatedIndex();
+ break;
+ case RESOURCE:
+ writeResource(getUriResourcePath());
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void writeTemplatedIndex() throws IOException {
+ MyNavigationTemplate t = MyNavigationTemplate.getCachedTemplate(mContext,
+ R.raw.my_navigation);
+ Cursor cursor = mContext.getContentResolver().query(
+ Uri.parse("content://com.android.browser.mynavigation/websites"),
+ new String[] {
+ "url", "title", "thumbnail"
+ },
+ null, null, null);
+
+ t.assignLoop("my_navigation", new MyNavigationTemplate.CursorListEntityWrapper(cursor) {
+ @Override
+ public void writeValue(OutputStream stream, String key) throws IOException {
+ Cursor cursor = getCursor();
+ if (key.equals("url")) {
+ stream.write(htmlEncode(cursor.getString(0)));
+ } else if (key.equals("title")) {
+ String title = cursor.getString(1);
+ if (title == null || title.length() == 0) {
+ title = mContext.getString(R.string.my_navigation_add);
+ }
+ stream.write(htmlEncode(title));
+ } else if (key.equals("thumbnail")) {
+ stream.write("data:image/png".getBytes());
+ stream.write(htmlEncode(cursor.getString(0)));
+ stream.write(";base64,".getBytes());
+ byte[] thumb = cursor.getBlob(2);
+ stream.write(Base64.encode(thumb, Base64.DEFAULT));
+ }
+ }
+ });
+ t.write(mOutput);
+ cursor.close();
+ }
+
+ byte[] htmlEncode(String s) {
+ return TextUtils.htmlEncode(s).getBytes();
+ }
+
+ String getUriResourcePath() {
+ final Pattern pattern = Pattern.compile("/?res/([\\w/]+)");
+ Matcher m = pattern.matcher(mUri.getPath());
+ if (m.matches()) {
+ return m.group(1);
+ } else {
+ return mUri.getPath();
+ }
+ }
+
+ void writeResource(String fileName) throws IOException {
+ Resources res = mContext.getResources();
+ String packageName = R.class.getPackage().getName();
+ int id = res.getIdentifier(fileName, null, packageName);
+ if (id != 0) {
+ InputStream in = res.openRawResource(id);
+ byte[] buf = new byte[4096];
+ int read;
+ while ((read = in.read(buf)) > 0) {
+ mOutput.write(buf, 0, read);
+ }
+ }
+ }
+
+ void writeString(String str) throws IOException {
+ mOutput.write(str.getBytes());
+ }
+
+ void writeString(String str, int offset, int count) throws IOException {
+ mOutput.write(str.getBytes(), offset, count);
+ }
+
+ void cleanup() {
+ try {
+ mOutput.close();
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Failed to close pipe!", e);
+ }
+ }
+}
diff --git a/src/com/android/browser/mynavigation/MyNavigationTemplate.java b/src/com/android/browser/mynavigation/MyNavigationTemplate.java
new file mode 100755
index 0000000..85d1baf
--- /dev/null
+++ b/src/com/android/browser/mynavigation/MyNavigationTemplate.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser.mynavigation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.util.TypedValue;
+import android.util.Log;
+
+import com.android.browser.R;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MyNavigationTemplate {
+
+ private static final String LOGTAG = "MyNavigationTemplate";
+ private static HashMap<Integer, MyNavigationTemplate> sCachedTemplates =
+ new HashMap<Integer, MyNavigationTemplate>();
+ private static boolean sCountryChanged = false;
+ private static String sCurrentCountry = "US";
+
+ private List<Entity> mTemplate;
+ private HashMap<String, Object> mData = new HashMap<String, Object>();
+
+ public static MyNavigationTemplate getCachedTemplate(Context context, int id) {
+
+ String changeToCountry = context.getResources().getConfiguration().locale
+ .getDisplayCountry();
+ Log.d(LOGTAG, "MyNavigationTemplate.getCachedTemplate() display country :"
+ + changeToCountry + ", before country :" + sCurrentCountry);
+ if (changeToCountry != null && !changeToCountry.equals(sCurrentCountry)) {
+ sCountryChanged = true;
+ sCurrentCountry = changeToCountry;
+ }
+ synchronized (sCachedTemplates) {
+ MyNavigationTemplate template = sCachedTemplates.get(id);
+ if (template == null || sCountryChanged) {
+ sCountryChanged = false;
+ template = new MyNavigationTemplate(context, id);
+ sCachedTemplates.put(id, template);
+ }
+ return template.copy();
+ }
+ }
+
+ interface Entity {
+ void write(OutputStream stream, EntityData params) throws IOException;
+ }
+
+ interface EntityData {
+ void writeValue(OutputStream stream, String key) throws IOException;
+
+ ListEntityIterator getListIterator(String key);
+ }
+
+ interface ListEntityIterator extends EntityData {
+ void reset();
+
+ boolean moveToNext();
+ }
+
+ static class StringEntity implements Entity {
+ byte[] mValue;
+
+ public StringEntity(String value) {
+ mValue = value.getBytes();
+ }
+
+ @Override
+ public void write(OutputStream stream, EntityData params) throws IOException {
+ stream.write(mValue);
+ }
+ }
+
+ static class SimpleEntity implements Entity {
+ String mKey;
+
+ public SimpleEntity(String key) {
+ mKey = key;
+ }
+
+ @Override
+ public void write(OutputStream stream, EntityData params) throws IOException {
+ params.writeValue(stream, mKey);
+ }
+ }
+
+ static class ListEntity implements Entity {
+ String mKey;
+ MyNavigationTemplate mSubTemplate;
+
+ public ListEntity(Context context, String key, String subTemplate) {
+ mKey = key;
+ mSubTemplate = new MyNavigationTemplate(context, subTemplate);
+ }
+
+ @Override
+ public void write(OutputStream stream, EntityData params) throws IOException {
+ ListEntityIterator iter = params.getListIterator(mKey);
+ if (null == iter) {
+ return;
+ }
+ iter.reset();
+ while (iter.moveToNext()) {
+ mSubTemplate.write(stream, iter);
+ }
+ }
+ }
+
+ public abstract static class CursorListEntityWrapper implements ListEntityIterator {
+ private Cursor mCursor;
+
+ public CursorListEntityWrapper(Cursor cursor) {
+ mCursor = cursor;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return mCursor.moveToNext();
+ }
+
+ @Override
+ public void reset() {
+ mCursor.moveToPosition(-1);
+ }
+
+ @Override
+ public ListEntityIterator getListIterator(String key) {
+ return null;
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+ }
+
+ static class HashMapEntityData implements EntityData {
+ HashMap<String, Object> mData;
+
+ public HashMapEntityData(HashMap<String, Object> map) {
+ mData = map;
+ }
+
+ @Override
+ public ListEntityIterator getListIterator(String key) {
+ return (ListEntityIterator) mData.get(key);
+ }
+
+ @Override
+ public void writeValue(OutputStream stream, String key) throws IOException {
+ stream.write((byte[]) mData.get(key));
+ }
+ }
+
+ private MyNavigationTemplate(Context context, int tid) {
+ this(context, readRaw(context, tid));
+ }
+
+ private MyNavigationTemplate(Context context, String template) {
+ mTemplate = new ArrayList<Entity>();
+ template = replaceConsts(context, template);
+ parseTemplate(context, template);
+ }
+
+ private MyNavigationTemplate(MyNavigationTemplate copy) {
+ mTemplate = copy.mTemplate;
+ }
+
+ MyNavigationTemplate copy() {
+ return new MyNavigationTemplate(this);
+ }
+
+ void parseTemplate(Context context, String template) {
+ final Pattern pattern = Pattern.compile("<%([=\\{])\\s*(\\w+)\\s*%>");
+ Matcher m = pattern.matcher(template);
+ int start = 0;
+ while (m.find()) {
+ String staticPart = template.substring(start, m.start());
+ if (staticPart.length() > 0) {
+ mTemplate.add(new StringEntity(staticPart));
+ }
+ String type = m.group(1);
+ String name = m.group(2);
+ if (type.equals("=")) {
+ mTemplate.add(new SimpleEntity(name));
+ } else if (type.equals("{")) {
+ Pattern p = Pattern.compile("<%\\}\\s*" + Pattern.quote(name) + "\\s*%>");
+ Matcher end = p.matcher(template);
+ if (end.find(m.end())) {
+ start = m.end();
+ m.region(end.end(), template.length());
+ String subTemplate = template.substring(start, end.start());
+ mTemplate.add(new ListEntity(context, name, subTemplate));
+ start = end.end();
+ continue;
+ }
+ }
+ start = m.end();
+ }
+ String staticPart = template.substring(start, template.length());
+ if (staticPart.length() > 0) {
+ mTemplate.add(new StringEntity(staticPart));
+ }
+ }
+
+ public void assign(String name, String value) {
+ mData.put(name, value.getBytes());
+ }
+
+ public void assignLoop(String name, ListEntityIterator iter) {
+ mData.put(name, iter);
+ }
+
+ public void write(OutputStream stream) throws IOException {
+ write(stream, new HashMapEntityData(mData));
+ }
+
+ public void write(OutputStream stream, EntityData data) throws IOException {
+ for (Entity ent : mTemplate) {
+ ent.write(stream, data);
+ }
+ }
+
+ private static String replaceConsts(Context context, String template) {
+ final Pattern pattern = Pattern.compile("<%@\\s*(\\w+/\\w+)\\s*%>");
+ final Resources res = context.getResources();
+ final String packageName = R.class.getPackage().getName();
+ Matcher m = pattern.matcher(template);
+ StringBuffer sb = new StringBuffer();
+ while (m.find()) {
+ String name = m.group(1);
+ if (name.startsWith("drawable/")) {
+ m.appendReplacement(sb, "res/" + name);
+ } else {
+ int id = res.getIdentifier(name, null, packageName);
+ if (id != 0) {
+ TypedValue value = new TypedValue();
+ res.getValue(id, value, true);
+ String replacement;
+ if (value.type == TypedValue.TYPE_DIMENSION) {
+ float dimen = res.getDimension(id);
+ int dimeni = (int) dimen;
+ if (dimeni == dimen) {
+ replacement = Integer.toString(dimeni);
+ } else {
+ replacement = Float.toString(dimen);
+ }
+ } else {
+ replacement = value.coerceToString().toString();
+ }
+ m.appendReplacement(sb, replacement);
+ }
+ }
+ }
+ m.appendTail(sb);
+ return sb.toString();
+ }
+
+ private static String readRaw(Context context, int id) {
+ InputStream ins = context.getResources().openRawResource(id);
+ try {
+ byte[] buf = new byte[ins.available()];
+ ins.read(buf);
+ return new String(buf, "utf-8");
+ } catch (IOException ex) {
+ return "<html><body>Error</body></html>";
+ }
+ }
+}
diff --git a/src/com/android/browser/mynavigation/MyNavigationUtil.java b/src/com/android/browser/mynavigation/MyNavigationUtil.java
new file mode 100755
index 0000000..3b1836d
--- /dev/null
+++ b/src/com/android/browser/mynavigation/MyNavigationUtil.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser.mynavigation;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+public class MyNavigationUtil {
+
+ public static final String ID = "_id";
+ public static final String URL = "url";
+ public static final String TITLE = "title";
+ public static final String DATE_CREATED = "created";
+ public static final String WEBSITE = "website";
+ public static final String FAVICON = "favicon";
+ public static final String THUMBNAIL = "thumbnail";
+ public static final int WEBSITE_NUMBER = 12;
+
+ public static final String AUTHORITY = "com.android.browser.mynavigation";
+ public static final String MY_NAVIGATION = "content://" + AUTHORITY + "/" + "websites";
+ public static final Uri MY_NAVIGATION_URI = Uri
+ .parse("content://com.android.browser.mynavigation/websites");
+ public static final String DEFAULT_THUMB = "default_thumb";
+ public static final String LOGTAG = "MyNavigationUtil";
+
+ public static boolean isDefaultMyNavigation(String url) {
+ if (url != null && url.startsWith("ae://") && url.endsWith("add-fav")) {
+ Log.d(LOGTAG, "isDefaultMyNavigation will return true.");
+ return true;
+ }
+ return false;
+ }
+
+ public static String getMyNavigationUrl(String srcUrl) {
+ String srcPrefix = "data:image/png";
+ String srcSuffix = ";base64,";
+ if (srcUrl != null && srcUrl.startsWith(srcPrefix)) {
+ int indexPrefix = srcPrefix.length();
+ int indexSuffix = srcUrl.indexOf(srcSuffix);
+ return srcUrl.substring(indexPrefix, indexSuffix);
+ }
+ return "";
+ }
+
+ public static boolean isMyNavigationUrl(Context context, String itemUrl) {
+ ContentResolver cr = context.getContentResolver();
+ Cursor cursor = null;
+ try {
+ cursor = cr.query(MyNavigationUtil.MY_NAVIGATION_URI,
+ new String[] {
+ MyNavigationUtil.TITLE
+ }, "url = ?", new String[] {
+ itemUrl
+ }, null);
+ if (null != cursor && cursor.moveToFirst()) {
+ Log.d(LOGTAG, "isMyNavigationUrl will return true.");
+ return true;
+ }
+ } catch (IllegalStateException e) {
+ Log.e(LOGTAG, "isMyNavigationUrl", e);
+ } finally {
+ if (null != cursor) {
+ cursor.close();
+ }
+ }
+ return false;
+ }
+
+ private static final String ACCEPTABLE_WEBSITE_SCHEMES[] = {
+ "http:",
+ "https:",
+ "about:",
+ "data:",
+ "javascript:",
+ "file:",
+ "content:"
+ };
+
+ public static boolean urlHasAcceptableScheme(String url) {
+ if (url == null) {
+ return false;
+ }
+
+ for (int i = 0; i < ACCEPTABLE_WEBSITE_SCHEMES.length; i++) {
+ if (url.startsWith(ACCEPTABLE_WEBSITE_SCHEMES[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/com/android/browser/provider/MyNavigationProvider.java b/src/com/android/browser/provider/MyNavigationProvider.java
new file mode 100755
index 0000000..3dc2954
--- /dev/null
+++ b/src/com/android/browser/provider/MyNavigationProvider.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.browser.provider;
+
+import android.content.Context;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.CursorFactory;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.text.TextUtils;
+import android.util.Log;
+import android.webkit.WebResourceResponse;
+
+import com.android.browser.BrowserSettings;
+import com.android.browser.homepages.RequestHandler;
+import com.android.browser.mynavigation.MyNavigationUtil;
+import com.android.browser.mynavigation.MyNavigationRequestHandler;
+import com.android.browser.provider.BrowserProvider2;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import com.android.browser.R;
+
+public class MyNavigationProvider extends ContentProvider {
+
+ private static final String LOGTAG = "MyNavigationProvider";
+ private static final String TABLE_WEB_SITES = "websites";
+ private static final int WEB_SITES_ALL = 0;
+ private static final int WEB_SITES_ID = 1;
+ private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+ static {
+ URI_MATCHER.addURI(MyNavigationUtil.AUTHORITY, "websites", WEB_SITES_ALL);
+ URI_MATCHER.addURI(MyNavigationUtil.AUTHORITY, "websites/#", WEB_SITES_ID);
+ }
+ private static final Uri NOTIFICATION_URI = MyNavigationUtil.MY_NAVIGATION_URI;
+
+ private SiteNavigationDatabaseHelper mOpenHelper;
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ // Current not used, just return 0
+ return 0;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // Current not used, just return null
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ // Current not used, just return null
+ return null;
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new SiteNavigationDatabaseHelper(this.getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(TABLE_WEB_SITES);
+ switch (URI_MATCHER.match(uri)) {
+ case WEB_SITES_ALL:
+ break;
+ case WEB_SITES_ID:
+ qb.appendWhere(MyNavigationUtil.ID + "=" + uri.getPathSegments().get(0));
+ break;
+ default:
+ Log.e(LOGTAG, "query Unknown URI: " + uri);
+ return null;
+ }
+
+ String orderBy;
+ if (TextUtils.isEmpty(sortOrder)) {
+ orderBy = null;
+ } else {
+ orderBy = sortOrder;
+ }
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, orderBy);
+ if (c != null) {
+ c.setNotificationUri(getContext().getContentResolver(), NOTIFICATION_URI);
+ }
+ return c;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count = 0;
+
+ switch (URI_MATCHER.match(uri)) {
+ case WEB_SITES_ALL:
+ count = db.update(TABLE_WEB_SITES, values, selection, selectionArgs);
+ break;
+ case WEB_SITES_ID:
+ String newIdSelection = MyNavigationUtil.ID + "=" + uri.getLastPathSegment()
+ + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
+ count = db.update(TABLE_WEB_SITES, values, newIdSelection, selectionArgs);
+ break;
+ default:
+ Log.e(LOGTAG, "update Unknown URI: " + uri);
+ return count;
+ }
+
+ if (count > 0) {
+ ContentResolver cr = getContext().getContentResolver();
+ cr.notifyChange(uri, null);
+ }
+ return count;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) {
+ try {
+ ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();
+ final ParcelFileDescriptor write = pipes[1];
+ AssetFileDescriptor afd = new AssetFileDescriptor(write, 0, -1);
+ new MyNavigationRequestHandler(getContext(), uri, afd.createOutputStream())
+ .start();
+ return pipes[0];
+ } catch (IOException e) {
+ Log.e(LOGTAG, "Failed to handle request: " + uri, e);
+ return null;
+ }
+ }
+
+ public static WebResourceResponse shouldInterceptRequest(Context context,
+ String url) {
+ try {
+ if (MyNavigationUtil.MY_NAVIGATION.equals(url)) {
+ Uri uri = Uri.parse(url);
+ if (MyNavigationUtil.AUTHORITY.equals(uri.getAuthority())) {
+ InputStream ins = context.getContentResolver()
+ .openInputStream(uri);
+ return new WebResourceResponse("text/html", "utf-8", ins);
+ }
+ }
+ boolean listFiles = BrowserSettings.getInstance().isDebugEnabled();
+ if (listFiles && interceptFile(url)) {
+ PipedInputStream ins = new PipedInputStream();
+ PipedOutputStream outs = new PipedOutputStream(ins);
+ new RequestHandler(context, Uri.parse(url), outs).start();
+ return new WebResourceResponse("text/html", "utf-8", ins);
+ }
+ } catch (Exception e) {}
+ return null;
+ }
+
+ private static boolean interceptFile(String url) {
+ if (!url.startsWith("file:///")) {
+ return false;
+ }
+ String fpath = url.substring(7);
+ File f = new File(fpath);
+ if (!f.isDirectory()) {
+ return false;
+ }
+ return true;
+ }
+
+ private class SiteNavigationDatabaseHelper extends SQLiteOpenHelper {
+ private Context mContext;
+ static final String DATABASE_NAME = "mynavigation.db";
+
+ public SiteNavigationDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, 1); // "1" is the db version here
+ // TODO Auto-generated constructor stub
+ mContext = context;
+ }
+
+ public SiteNavigationDatabaseHelper(Context context, String name,
+ CursorFactory factory, int version) {
+ super(context, name, factory, version);
+ // TODO Auto-generated constructor stub
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // TODO Auto-generated method stub
+ createWebsitesTable(db);
+ initWebsitesTable(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
+ // TODO Auto-generated method stub
+ }
+
+ private void createWebsitesTable(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE websites (" +
+ MyNavigationUtil.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ MyNavigationUtil.URL + " TEXT," +
+ MyNavigationUtil.TITLE + " TEXT," +
+ MyNavigationUtil.DATE_CREATED + " LONG," +
+ MyNavigationUtil.WEBSITE + " INTEGER," +
+ MyNavigationUtil.THUMBNAIL + " BLOB DEFAULT NULL," +
+ MyNavigationUtil.FAVICON + " BLOB DEFAULT NULL," +
+ MyNavigationUtil.DEFAULT_THUMB + " TEXT" +
+ ");");
+ }
+
+ // initial table , insert websites to table websites
+ private void initWebsitesTable(SQLiteDatabase db) {
+ int WebsiteNumber = MyNavigationUtil.WEBSITE_NUMBER;
+ for (int i = 0; i < WebsiteNumber; i++) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ Bitmap bm = BitmapFactory.decodeResource(mContext.getResources(),
+ R.raw.my_navigation_add);
+ bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+ ContentValues values = new ContentValues();
+ values.put(MyNavigationUtil.URL, "ae://" + (i + 1) + "add-fav");
+ values.put(MyNavigationUtil.TITLE, mContext.getString(R.string.my_navigation_add));
+ values.put(MyNavigationUtil.DATE_CREATED, 0 + "");
+ values.put(MyNavigationUtil.WEBSITE, 1 + "");
+ values.put(MyNavigationUtil.THUMBNAIL, os.toByteArray());
+ db.insertOrThrow(TABLE_WEB_SITES, MyNavigationUtil.URL, values);
+ }
+ }
+ }
+}