diff options
Diffstat (limited to 'src/com')
23 files changed, 1685 insertions, 355 deletions
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java index cf3fe70..e827a7e 100644 --- a/src/com/android/browser/AddBookmarkPage.java +++ b/src/com/android/browser/AddBookmarkPage.java @@ -18,18 +18,14 @@ package com.android.browser; import android.app.Activity; 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.net.ParseException; import android.net.WebAddress; import android.os.Bundle; import android.provider.Browser; import android.view.View; import android.view.Window; -import android.webkit.WebIconDatabase; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; @@ -46,11 +42,6 @@ public class AddBookmarkPage extends Activity { private View mCancelButton; private boolean mEditingExisting; private Bundle mMap; - - private static final String[] mProjection = - { "_id", "url", "bookmark", "created", "title", "visits" }; - private static final String WHERE_CLAUSE = "url = ?"; - private final String[] SELECTION_ARGS = new String[1]; private View.OnClickListener mSaveBookmark = new View.OnClickListener() { public void onClick(View v) { @@ -151,70 +142,7 @@ public class AddBookmarkPage extends Activity { setResult(RESULT_OK, (new Intent()).setAction( getIntent().toString()).putExtras(mMap)); } else { - // Want to append to the beginning of the list - long creationTime = new Date().getTime(); - SELECTION_ARGS[0] = url; - ContentResolver cr = getContentResolver(); - Cursor c = cr.query(Browser.BOOKMARKS_URI, - mProjection, - WHERE_CLAUSE, - SELECTION_ARGS, - null); - ContentValues map = new ContentValues(); - if (c.moveToFirst() && c.getInt(c.getColumnIndexOrThrow( - Browser.BookmarkColumns.BOOKMARK)) == 0) { - // This means we have been to this site but not bookmarked - // it, so convert the history item to a bookmark - map.put(Browser.BookmarkColumns.CREATED, creationTime); - map.put(Browser.BookmarkColumns.TITLE, title); - map.put(Browser.BookmarkColumns.BOOKMARK, 1); - cr.update(Browser.BOOKMARKS_URI, map, - "_id = " + c.getInt(0), null); - } else { - int count = c.getCount(); - boolean matchedTitle = false; - for (int i = 0; i < count; i++) { - // One or more bookmarks already exist for this site. - // Check the names of each - c.moveToPosition(i); - if (c.getString(c.getColumnIndexOrThrow( - Browser.BookmarkColumns.TITLE)).equals(title)) { - // The old bookmark has the same name. - // Update its creation time. - map.put(Browser.BookmarkColumns.CREATED, - creationTime); - cr.update(Browser.BOOKMARKS_URI, map, - "_id = " + c.getInt(0), null); - matchedTitle = true; - } - } - if (!matchedTitle) { - // Adding a bookmark for a site the user has visited, - // or a new bookmark (with a different name) for a site - // the user has visited - map.put(Browser.BookmarkColumns.TITLE, title); - map.put(Browser.BookmarkColumns.URL, url); - map.put(Browser.BookmarkColumns.CREATED, creationTime); - map.put(Browser.BookmarkColumns.BOOKMARK, 1); - map.put(Browser.BookmarkColumns.DATE, 0); - int visits = 0; - if (count > 0) { - // The user has already bookmarked, and possibly - // visited this site. However, they are creating - // a new bookmark with the same url but a different - // name. The new bookmark should have the same - // number of visits as the already created bookmark. - visits = c.getInt(c.getColumnIndexOrThrow( - Browser.BookmarkColumns.VISITS)); - } - // Bookmark starts with 3 extra visits so that it will - // bubble up in the most visited and goto search box - map.put(Browser.BookmarkColumns.VISITS, visits + 3); - cr.insert(Browser.BOOKMARKS_URI, map); - } - } - WebIconDatabase.getInstance().retainIconForPageUrl(url); - c.deactivate(); + Bookmarks.addBookmark(null, getContentResolver(), url, title); setResult(RESULT_OK); } } catch (IllegalStateException e) { diff --git a/src/com/android/browser/BookmarkGridPage.java b/src/com/android/browser/BookmarkGridPage.java new file mode 100644 index 0000000..a9db7ac --- /dev/null +++ b/src/com/android/browser/BookmarkGridPage.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.ContentObserver; +import android.database.DataSetObserver; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Browser; +import android.provider.Browser.BookmarkColumns; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.util.ArrayList; + +public class BookmarkGridPage extends Activity { + private final static int SPACING = 10; + private BookmarkGrid mGridView; + private BookmarkGridAdapter mAdapter; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mGridView = new BookmarkGrid(this); + mGridView.setNumColumns(3); + mAdapter = new BookmarkGridAdapter(this); + mGridView.setAdapter(mAdapter); + mGridView.setFocusable(true); + mGridView.setFocusableInTouchMode(true); + mGridView.setSelector(android.R.drawable.gallery_thumb); + mGridView.setVerticalSpacing(SPACING); + mGridView.setHorizontalSpacing(SPACING); + setContentView(mGridView); + mGridView.requestFocus(); + } + + private class BookmarkGrid extends GridView { + public BookmarkGrid(Context context) { + super(context); + } + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + int thumbHeight = (h - 2 * (SPACING + getListPaddingTop() + + getListPaddingBottom())) / 3; + mAdapter.heightChanged(thumbHeight); + super.onSizeChanged(w, h, oldw, oldh); + } + } + + private class BookmarkGridAdapter implements ListAdapter { + private ArrayList<DataSetObserver> mDataObservers; + private Context mContext; // Context to use to inflate views + private Cursor mCursor; + private int mThumbHeight; + + public BookmarkGridAdapter(Context context) { + mContext = context; + mDataObservers = new ArrayList<DataSetObserver>(); + String whereClause = Browser.BookmarkColumns.BOOKMARK + " != 0"; + String orderBy = Browser.BookmarkColumns.VISITS + " DESC"; + mCursor = managedQuery(Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, whereClause, null, orderBy); + mCursor.registerContentObserver(new ChangeObserver()); + mGridView.setOnItemClickListener( + new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, + int position, long id) { + mCursor.moveToPosition(position); + String url = mCursor.getString( + Browser.HISTORY_PROJECTION_URL_INDEX); + Intent intent = (new Intent()).setAction(url); + getParent().setResult(RESULT_OK, intent); + finish(); + }}); + } + + void heightChanged(int newHeight) { + mThumbHeight = newHeight; + } + + private class ChangeObserver extends ContentObserver { + public ChangeObserver() { + super(new Handler()); + } + + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + BookmarkGridAdapter.this.refreshData(); + } + } + + void refreshData() { + mCursor.requery(); + for (DataSetObserver o : mDataObservers) { + o.onChanged(); + } + } + + /* (non-Javadoc) + * @see android.widget.ListAdapter#areAllItemsSelectable() + */ + public boolean areAllItemsEnabled() { + return true; + } + + /* (non-Javadoc) + * @see android.widget.ListAdapter#isSelectable(int) + */ + public boolean isEnabled(int position) { + if (position >= 0 && position < mCursor.getCount()) { + return true; + } + return false; + } + + /* (non-Javadoc) + * @see android.widget.Adapter#getCount() + */ + public int getCount() { + return mCursor.getCount(); + } + + /* (non-Javadoc) + * @see android.widget.Adapter#getItem(int) + */ + public Object getItem(int position) { + return null; + } + + /* (non-Javadoc) + * @see android.widget.Adapter#getItemId(int) + */ + public long getItemId(int position) { + return position; + } + + /* (non-Javadoc) + * @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup) + */ + public View getView(int position, View convertView, ViewGroup parent) { + View v = null; + if (convertView != null) { + v = convertView; + } else { + LayoutInflater factory = LayoutInflater.from(mContext); + v = factory.inflate(R.layout.bookmark_thumbnail, null); + } + ImageView thumb = (ImageView) v.findViewById(R.id.thumb); + TextView tv = (TextView) v.findViewById(R.id.label); + + mCursor.moveToPosition(position); + tv.setText(mCursor.getString( + Browser.HISTORY_PROJECTION_TITLE_INDEX)); + byte[] data = mCursor.getBlob( + Browser.HISTORY_PROJECTION_THUMBNAIL_INDEX); + if (data == null) { + // Backup is to show the favicon + data = mCursor.getBlob( + Browser.HISTORY_PROJECTION_FAVICON_INDEX); + thumb.setScaleType(ImageView.ScaleType.CENTER); + } else { + thumb.setScaleType(ImageView.ScaleType.FIT_XY); + } + if (data != null) { + thumb.setImageBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length)); + } else { + thumb.setImageResource(R.drawable.app_web_browser_sm); + thumb.setScaleType(ImageView.ScaleType.CENTER); + } + ViewGroup.LayoutParams lp = thumb.getLayoutParams(); + if (lp.height != mThumbHeight) { + lp.height = mThumbHeight; + thumb.requestLayout(); + } + return v; + } + + /* (non-Javadoc) + * @see android.widget.Adapter#registerDataSetObserver(android.database.DataSetObserver) + */ + public void registerDataSetObserver(DataSetObserver observer) { + mDataObservers.add(observer); + } + + /* (non-Javadoc) + * @see android.widget.Adapter#hasStableIds() + */ + public boolean hasStableIds() { + return true; + } + + /* (non-Javadoc) + * @see android.widget.Adapter#unregisterDataSetObserver(android.database.DataSetObserver) + */ + public void unregisterDataSetObserver(DataSetObserver observer) { + mDataObservers.remove(observer); + } + + public int getItemViewType(int position) { + return 0; + } + + public int getViewTypeCount() { + return 1; + } + + public boolean isEmpty() { + return getCount() == 0; + } + } +} diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java new file mode 100644 index 0000000..97e6b20 --- /dev/null +++ b/src/com/android/browser/Bookmarks.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.Browser; +import android.util.Log; +import android.webkit.WebIconDatabase; +import android.widget.Toast; + +import java.util.Date; + +/** + * This class is purely to have a common place for adding/deleting bookmarks. + */ +/* package */ class Bookmarks { + private static final String WHERE_CLAUSE + = "url = ? OR url = ? OR url = ? OR url = ?"; + private static final String WHERE_CLAUSE_SECURE = "url = ? OR url = ?"; + + private static String[] SELECTION_ARGS; + + /** + * Add a bookmark to the database. + * @param context Context of the calling Activity. This is used to make + * Toast confirming that the bookmark has been added. If the + * caller provides null, the Toast will not be shown. + * @param cr The ContentResolver being used to add the bookmark to the db. + * @param url URL of the website to be bookmarked. + * @param name Provided name for the bookmark. + */ + /* package */ static void addBookmark(Context context, + ContentResolver cr, String url, String name) { + // Want to append to the beginning of the list + long creationTime = new Date().getTime(); + // First we check to see if the user has already visited this + // site. They may have bookmarked it in a different way from + // how it's stored in the database, so allow different combos + // to map to the same url. + boolean secure = false; + String compareString = url; + if (compareString.startsWith("http://")) { + compareString = compareString.substring(7); + } else if (compareString.startsWith("https://")) { + compareString = compareString.substring(8); + secure = true; + } + if (compareString.startsWith("www.")) { + compareString = compareString.substring(4); + } + if (secure) { + SELECTION_ARGS = new String[2]; + SELECTION_ARGS[0] = "https://" + compareString; + SELECTION_ARGS[1] = "https://www." + compareString; + } else { + SELECTION_ARGS = new String[4]; + SELECTION_ARGS[0] = compareString; + SELECTION_ARGS[1] = "www." + compareString; + SELECTION_ARGS[2] = "http://" + compareString; + SELECTION_ARGS[3] = "http://" + SELECTION_ARGS[1]; + } + Cursor cursor = cr.query(Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + secure ? WHERE_CLAUSE_SECURE : WHERE_CLAUSE, + SELECTION_ARGS, + null); + ContentValues map = new ContentValues(); + if (cursor.moveToFirst() && cursor.getInt( + Browser.HISTORY_PROJECTION_BOOKMARK_INDEX) == 0) { + // This means we have been to this site but not bookmarked + // it, so convert the history item to a bookmark + map.put(Browser.BookmarkColumns.CREATED, creationTime); + map.put(Browser.BookmarkColumns.TITLE, name); + map.put(Browser.BookmarkColumns.BOOKMARK, 1); + cr.update(Browser.BOOKMARKS_URI, map, + "_id = " + cursor.getInt(0), null); + } else { + int count = cursor.getCount(); + boolean matchedTitle = false; + for (int i = 0; i < count; i++) { + // One or more bookmarks already exist for this site. + // Check the names of each + cursor.moveToPosition(i); + if (cursor.getString(Browser.HISTORY_PROJECTION_TITLE_INDEX) + .equals(name)) { + // The old bookmark has the same name. + // Update its creation time. + map.put(Browser.BookmarkColumns.CREATED, + creationTime); + cr.update(Browser.BOOKMARKS_URI, map, + "_id = " + cursor.getInt(0), null); + matchedTitle = true; + break; + } + } + if (!matchedTitle) { + // Adding a bookmark for a site the user has visited, + // or a new bookmark (with a different name) for a site + // the user has visited + map.put(Browser.BookmarkColumns.TITLE, name); + map.put(Browser.BookmarkColumns.URL, url); + map.put(Browser.BookmarkColumns.CREATED, creationTime); + map.put(Browser.BookmarkColumns.BOOKMARK, 1); + map.put(Browser.BookmarkColumns.DATE, 0); + int visits = 0; + if (count > 0) { + // The user has already bookmarked, and possibly + // visited this site. However, they are creating + // a new bookmark with the same url but a different + // name. The new bookmark should have the same + // number of visits as the already created bookmark. + visits = cursor.getInt( + Browser.HISTORY_PROJECTION_VISITS_INDEX); + } + // Bookmark starts with 3 extra visits so that it will + // bubble up in the most visited and goto search box + map.put(Browser.BookmarkColumns.VISITS, visits + 3); + cr.insert(Browser.BOOKMARKS_URI, map); + } + } + WebIconDatabase.getInstance().retainIconForPageUrl(url); + cursor.deactivate(); + if (context != null) { + Toast.makeText(context, R.string.added_to_bookmarks, + Toast.LENGTH_LONG).show(); + } + } + + /** + * Remove a bookmark from the database. If the url is a visited site, it + * will remain in the database, but only as a history item, and not as a + * bookmarked site. + * @param context Context of the calling Activity. This is used to make + * Toast confirming that the bookmark has been removed. If the + * caller provides null, the Toast will not be shown. + * @param cr The ContentResolver being used to remove the bookmark. + * @param url URL of the website to be removed. + */ + /* package */ static void removeFromBookmarks(Context context, + ContentResolver cr, String url) { + Cursor cursor = cr.query( + Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + "url = ?", + new String[] { url }, + null); + boolean first = cursor.moveToFirst(); + // Should be in the database no matter what + if (!first) { + throw new AssertionError("URL is not in the database!"); + } + // Remove from bookmarks + WebIconDatabase.getInstance().releaseIconForPageUrl(url); + Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, + cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); + int numVisits = cursor.getInt( + Browser.HISTORY_PROJECTION_VISITS_INDEX); + if (0 == numVisits) { + cr.delete(uri, null, null); + } else { + // It is no longer a bookmark, but it is still a visited + // site. + ContentValues values = new ContentValues(); + values.put(Browser.BookmarkColumns.BOOKMARK, 0); + try { + cr.update(uri, values, null, null); + } catch (IllegalStateException e) { + Log.e("removeFromBookmarks", "no database!"); + } + } + if (context != null) { + Toast.makeText(context, R.string.removed_from_bookmarks, + Toast.LENGTH_LONG).show(); + } + cursor.deactivate(); + } +}
\ No newline at end of file diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index d0fa0bd..b28d892 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -28,6 +28,7 @@ import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; @@ -35,6 +36,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.DialogInterface.OnCancelListener; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetManager; @@ -107,11 +109,13 @@ import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; import android.webkit.HttpAuthHandler; +import android.webkit.PluginManager; import android.webkit.SslErrorHandler; import android.webkit.URLUtil; import android.webkit.WebChromeClient; import android.webkit.WebHistoryItem; import android.webkit.WebIconDatabase; +import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; @@ -121,6 +125,7 @@ import android.widget.TextView; import android.widget.Toast; import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -159,6 +164,8 @@ public class BrowserActivity extends Activity private SensorManager mSensorManager = null; + private WebStorage.QuotaUpdater mWebStorageQuotaUpdater = null; + // These are single-character shortcuts for searching popular sources. private static final int SHORTCUT_INVALID = 0; private static final int SHORTCUT_GOOGLE_SEARCH = 1; @@ -583,11 +590,6 @@ public class BrowserActivity extends Activity } copyBuildInfos(); - - // Refresh the plugin list. - if (mTabControl.getCurrentWebView() != null) { - mTabControl.getCurrentWebView().refreshPlugins(false); - } } catch (IOException e) { Log.e(TAG, "IO Exception: " + e); } @@ -634,16 +636,22 @@ public class BrowserActivity extends Activity } } + // Flag to enable the touchable browser bar with buttons + private final boolean CUSTOM_BROWSER_BAR = true; + @Override public void onCreate(Bundle icicle) { if (LOGV_ENABLED) { Log.v(LOGTAG, this + " onStart"); } super.onCreate(icicle); - this.requestWindowFeature(Window.FEATURE_LEFT_ICON); - this.requestWindowFeature(Window.FEATURE_RIGHT_ICON); - this.requestWindowFeature(Window.FEATURE_PROGRESS); - this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - + if (CUSTOM_BROWSER_BAR) { + this.requestWindowFeature(Window.FEATURE_NO_TITLE); + } else { + this.requestWindowFeature(Window.FEATURE_LEFT_ICON); + this.requestWindowFeature(Window.FEATURE_RIGHT_ICON); + this.requestWindowFeature(Window.FEATURE_PROGRESS); + this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + } // test the browser in OpenGL // requestWindowFeature(Window.FEATURE_OPENGL); @@ -668,8 +676,28 @@ public class BrowserActivity extends Activity mGenericFavicon = getResources().getDrawable( R.drawable.app_web_browser_sm); - mContentView = (FrameLayout) getWindow().getDecorView().findViewById( - com.android.internal.R.id.content); + FrameLayout frameLayout = (FrameLayout) getWindow().getDecorView() + .findViewById(com.android.internal.R.id.content); + if (CUSTOM_BROWSER_BAR) { + // This FrameLayout will hold the custom FrameLayout and a LinearLayout + // that contains the title bar and a FrameLayout, which + // holds everything else. + FrameLayout browserFrameLayout = (FrameLayout) LayoutInflater.from(this) + .inflate(R.layout.custom_screen, null); + mTitleBar = (TitleBar) browserFrameLayout.findViewById(R.id.title_bar); + mTitleBar.setBrowserActivity(this); + mContentView = (FrameLayout) browserFrameLayout.findViewById( + R.id.main_content); + mCustomViewContainer = (FrameLayout) browserFrameLayout + .findViewById(R.id.fullscreen_custom_content); + frameLayout.addView(browserFrameLayout, COVER_SCREEN_PARAMS); + } else { + mCustomViewContainer = new FrameLayout(this); + mCustomViewContainer.setBackgroundColor(Color.BLACK); + mContentView = new FrameLayout(this); + frameLayout.addView(mCustomViewContainer, COVER_SCREEN_PARAMS); + frameLayout.addView(mContentView, COVER_SCREEN_PARAMS); + } // Create the tab control and our initial tab mTabControl = new TabControl(this); @@ -748,6 +776,11 @@ public class BrowserActivity extends Activity // are not animating from the tab picker. attachTabToContentView(mTabControl.getCurrentTab()); } + // Read JavaScript flags if it exists. + String jsFlags = mSettings.getJsFlags(); + if (jsFlags.trim().length() != 0) { + mTabControl.getCurrentWebView().setJsFlags(jsFlags); + } /* enables registration for changes in network status from http stack */ @@ -765,6 +798,52 @@ public class BrowserActivity extends Activity } } }; + + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mPackageInstallationReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final String packageName = intent.getData() + .getSchemeSpecificPart(); + final boolean replacing = intent.getBooleanExtra( + Intent.EXTRA_REPLACING, false); + if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && replacing) { + // if it is replacing, refreshPlugins() when adding + return; + } + PackageManager pm = BrowserActivity.this.getPackageManager(); + PackageInfo pkgInfo = null; + try { + pkgInfo = pm.getPackageInfo(packageName, + PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + return; + } + if (pkgInfo != null) { + String permissions[] = pkgInfo.requestedPermissions; + if (permissions == null) { + return; + } + boolean permissionOk = false; + for (String permit : permissions) { + if (PluginManager.PLUGIN_PERMISSION.equals(permit)) { + permissionOk = true; + break; + } + } + if (permissionOk) { + PluginManager.getInstance(BrowserActivity.this) + .refreshPlugins( + Intent.ACTION_PACKAGE_ADDED + .equals(action)); + } + } + } + }; + registerReceiver(mPackageInstallationReceiver, filter); } @Override @@ -830,8 +909,8 @@ public class BrowserActivity extends Activity } else { if (mTabOverview != null && mAnimationCount == 0) { sendAnimateFromOverview(appTab, false, - needsLoad ? urlData : EMPTY_URL_DATA, TAB_OVERVIEW_DELAY, - null); + needsLoad ? urlData : EMPTY_URL_DATA, + TAB_OVERVIEW_DELAY, null); } else { // If the tab was the current tab, we have to attach // it to the view system again. @@ -1105,8 +1184,9 @@ public class BrowserActivity extends Activity return; } + mTabControl.resumeCurrentTab(); mActivityInPause = false; - resumeWebView(); + resumeWebViewTimers(); if (mWakeLock.isHeld()) { mHandler.removeMessages(RELEASE_WAKELOCK); @@ -1164,8 +1244,9 @@ public class BrowserActivity extends Activity return; } + mTabControl.pauseCurrentTab(); mActivityInPause = true; - if (mTabControl.getCurrentIndex() >= 0 && !pauseWebView()) { + if (mTabControl.getCurrentIndex() >= 0 && !pauseWebViewTimers()) { mWakeLock.acquire(); mHandler.sendMessageDelayed(mHandler .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT); @@ -1195,8 +1276,10 @@ public class BrowserActivity extends Activity super.onDestroy(); // Remove the current tab and sub window TabControl.Tab t = mTabControl.getCurrentTab(); - dismissSubWindow(t); - removeTabFromContentView(t); + if (t != null) { + dismissSubWindow(t); + removeTabFromContentView(t); + } // Destroy all the tabs mTabControl.destroy(); WebIconDatabase.getInstance().close(); @@ -1214,6 +1297,8 @@ public class BrowserActivity extends Activity // "com.android.masfproxyservice", // "com.android.masfproxyservice.MasfProxyService")); //stopService(proxyServiceIntent); + + unregisterReceiver(mPackageInstallationReceiver); } @Override @@ -1262,7 +1347,7 @@ public class BrowserActivity extends Activity mTabControl.freeMemory(); } - private boolean resumeWebView() { + private boolean resumeWebViewTimers() { if ((!mActivityInPause && !mPageStarted) || (mActivityInPause && mPageStarted)) { CookieSyncManager.getInstance().startSync(); @@ -1276,7 +1361,7 @@ public class BrowserActivity extends Activity } } - private boolean pauseWebView() { + private boolean pauseWebViewTimers() { if (mActivityInPause && !mPageStarted) { CookieSyncManager.getInstance().stopSync(); WebView w = mTabControl.getCurrentWebView(); @@ -1975,9 +2060,9 @@ public class BrowserActivity extends Activity // A wrapper function of {@link #openTabAndShow(UrlData, Message, boolean, String)} // that accepts url as string. - private void openTabAndShow(String url, final Message msg, + private TabControl.Tab openTabAndShow(String url, final Message msg, boolean closeOnExit, String appId) { - openTabAndShow(new UrlData(url), msg, closeOnExit, appId); + return openTabAndShow(new UrlData(url), msg, closeOnExit, appId); } // This method does a ton of stuff. It will attempt to create a new tab @@ -1988,7 +2073,7 @@ public class BrowserActivity extends Activity // the given Message. If the tab overview is already showing (i.e. this // method is called from TabListener.onClick(), the method will animate // away from the tab overview. - private void openTabAndShow(UrlData urlData, final Message msg, + private TabControl.Tab openTabAndShow(UrlData urlData, final Message msg, boolean closeOnExit, String appId) { final boolean newTab = mTabControl.getTabCount() != TabControl.MAX_TABS; final TabControl.Tab currentTab = mTabControl.getCurrentTab(); @@ -2017,9 +2102,10 @@ public class BrowserActivity extends Activity } // Animate from the Tab overview after any animations have // finished. - sendAnimateFromOverview( - mTabControl.createNewTab(closeOnExit, appId, urlData.mUrl), true, - urlData, delay, msg); + final TabControl.Tab tab = mTabControl.createNewTab( + closeOnExit, appId, urlData.mUrl); + sendAnimateFromOverview(tab, true, urlData, delay, msg); + return tab; } } else if (!urlData.isEmpty()) { // We should not have a msg here. @@ -2034,6 +2120,7 @@ public class BrowserActivity extends Activity urlData.loadIn(currentTab.getWebView()); } } + return currentTab; } private Animation createTabAnimation(final AnimatingView view, @@ -2255,14 +2342,15 @@ public class BrowserActivity extends Activity mTabListener = null; } - private void openTab(String url) { + private TabControl.Tab openTab(String url) { if (mSettings.openInBackground()) { TabControl.Tab t = mTabControl.createNewTab(); if (t != null) { t.getWebView().loadUrl(url); } + return t; } else { - openTabAndShow(url, null, false, null); + return openTabAndShow(url, null, false, null); } } @@ -2362,7 +2450,11 @@ public class BrowserActivity extends Activity // While the tab overview is animating or being shown, block changes // to the title. if (mAnimationCount == 0 && mTabOverview == null) { - setTitle(buildUrlTitle(url, title)); + if (CUSTOM_BROWSER_BAR) { + mTitleBar.setTitleAndUrl(title, url); + } else { + setTitle(buildUrlTitle(url, title)); + } } } @@ -2403,7 +2495,7 @@ public class BrowserActivity extends Activity * or an empty string if, for example, the URL in question is a * file:// URL with no hostname. */ - private static String buildTitleUrl(String url) { + /* package */ static String buildTitleUrl(String url) { String titleUrl = null; if (url != null) { @@ -2439,18 +2531,34 @@ public class BrowserActivity extends Activity if (mAnimationCount > 0 || mTabOverview != null) { return; } - Drawable[] array = new Drawable[2]; - PaintDrawable p = new PaintDrawable(Color.WHITE); - p.setCornerRadius(3f); - array[0] = p; - if (icon == null) { - array[1] = mGenericFavicon; + if (CUSTOM_BROWSER_BAR) { + Drawable[] array = new Drawable[3]; + array[0] = new PaintDrawable(Color.BLACK); + PaintDrawable p = new PaintDrawable(Color.WHITE); + array[1] = p; + if (icon == null) { + array[2] = mGenericFavicon; + } else { + array[2] = new BitmapDrawable(icon); + } + LayerDrawable d = new LayerDrawable(array); + d.setLayerInset(1, 1, 1, 1, 1); + d.setLayerInset(2, 2, 2, 2, 2); + mTitleBar.setFavicon(d); } else { - array[1] = new BitmapDrawable(icon); + Drawable[] array = new Drawable[2]; + PaintDrawable p = new PaintDrawable(Color.WHITE); + p.setCornerRadius(3f); + array[0] = p; + if (icon == null) { + array[1] = mGenericFavicon; + } else { + array[1] = new BitmapDrawable(icon); + } + LayerDrawable d = new LayerDrawable(array); + d.setLayerInset(1, 2, 2, 2, 2); + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d); } - LayerDrawable d = new LayerDrawable(array); - d.setLayerInset(1, 2, 2, 2, 2); - getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, d); } /** @@ -2528,9 +2636,9 @@ public class BrowserActivity extends Activity finish(); return; } - // call pauseWebView() now, we won't be able to call it in - // onPause() as the WebView won't be valid. - pauseWebView(); + // call pauseWebViewTimers() now, we won't be able to call + // it in onPause() as the WebView won't be valid. + pauseWebViewTimers(); removeTabFromContentView(current); mTabControl.removeTab(current); } @@ -2554,10 +2662,15 @@ public class BrowserActivity extends Activity // because of accumulated key events, // we should ignore it as browser is not active any more. WebView topWindow = getTopWindow(); - if (topWindow == null) + if (topWindow == null && mCustomView == null) return KeyTracker.State.NOT_TRACKING; if (keyCode == KeyEvent.KEYCODE_BACK) { + // Check if a custom view is currently showing and, if it is, hide it. + if (mCustomView != null) { + mWebChromeClient.onHideCustomView(); + return KeyTracker.State.DONE_TRACKING; + } // During animations, block the back key so that other animations // are not triggered and so that we don't end up destroying all the // WebViews before finishing the animation. @@ -2696,7 +2809,12 @@ public class BrowserActivity extends Activity loadURL(getTopWindow(), url); break; case R.id.open_newtab_context_menu_id: - openTab(url); + final TabControl.Tab parent = mTabControl + .getCurrentTab(); + final TabControl.Tab newTab = openTab(url); + if (newTab != parent) { + parent.addChildTab(newTab); + } break; case R.id.bookmark_context_menu_id: Intent intent = new Intent(BrowserActivity.this, @@ -2821,8 +2939,9 @@ public class BrowserActivity extends Activity if (!mPageStarted) { mPageStarted = true; - // if onResume() has been called, resumeWebView() does nothing. - resumeWebView(); + // if onResume() has been called, resumeWebViewTimers() does + // nothing. + resumeWebViewTimers(); } // reset sync timer to avoid sync starts during loading a page @@ -2857,6 +2976,47 @@ public class BrowserActivity extends Activity // Update the lock icon image only once we are done loading updateLockIconImage(mLockIconType); + // If this is a bookmarked site, add a screenshot to the database. + // FIXME: When should we update? Every time? + if (url != null) { + // copied from BrowserBookmarksAdapter + int query = url.indexOf('?'); + String noQuery = url; + if (query != -1) { + noQuery = url.substring(0, query); + } + String URL = noQuery + '?'; + String[] selArgs = new String[] { noQuery, URL }; + final String where = "(url == ? OR url GLOB ? || '*') AND bookmark == 1"; + final String[] projection = new String[] { Browser.BookmarkColumns._ID }; + ContentResolver cr = getContentResolver(); + final Cursor c = cr.query(Browser.BOOKMARKS_URI, projection, where, selArgs, null); + boolean succeed = c.moveToFirst(); + ContentValues values = null; + while (succeed) { + if (values == null) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + Picture thumbnail = view.capturePicture(); + // Height was arbitrarily chosen + Bitmap bm = Bitmap.createBitmap(100, 100, + Bitmap.Config.ARGB_4444); + Canvas canvas = new Canvas(bm); + // Scale chosen to be about one third, since we want + // roughly three rows/columns for bookmark page + canvas.scale(.3f, .3f); + thumbnail.draw(canvas); + bm.compress(Bitmap.CompressFormat.PNG, 100, os); + values = new ContentValues(); + values.put(Browser.BookmarkColumns.THUMBNAIL, + os.toByteArray()); + } + cr.update(ContentUris.withAppendedId(Browser.BOOKMARKS_URI, + c.getInt(0)), values, null, null); + succeed = c.moveToNext(); + } + c.close(); + } + // Performance probe if (false) { long[] sysCpu = new long[7]; @@ -2943,9 +3103,9 @@ public class BrowserActivity extends Activity if (mPageStarted) { mPageStarted = false; - // pauseWebView() will do nothing and return false if onPause() - // is not called yet. - if (pauseWebView()) { + // pauseWebViewTimers() will do nothing and return false if + // onPause() is not called yet. + if (pauseWebViewTimers()) { if (mWakeLock.isHeld()) { mHandler.removeMessages(RELEASE_WAKELOCK); mWakeLock.release(); @@ -3381,8 +3541,11 @@ public class BrowserActivity extends Activity // openTabAndShow will dispatch the message after creating the // new WebView. This will prevent another request from coming // in during the animation. - openTabAndShow(EMPTY_URL_DATA, msg, false, null); - parent.addChildTab(mTabControl.getCurrentTab()); + final TabControl.Tab newTab = + openTabAndShow(EMPTY_URL_DATA, msg, false, null); + if (newTab != parent) { + parent.addChildTab(newTab); + } WebView.WebViewTransport transport = (WebView.WebViewTransport) msg.obj; transport.setWebView(mTabControl.getCurrentWebView()); @@ -3488,8 +3651,13 @@ public class BrowserActivity extends Activity // Block progress updates to the title bar while the tab overview // is animating or being displayed. if (mAnimationCount == 0 && mTabOverview == null) { - getWindow().setFeatureInt(Window.FEATURE_PROGRESS, - newProgress * 100); + if (CUSTOM_BROWSER_BAR) { + mTitleBar.setProgress(newProgress); + } else { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + newProgress * 100); + + } } if (newProgress == 100) { @@ -3523,6 +3691,8 @@ public class BrowserActivity extends Activity url.length() >= SQLiteDatabase.SQLITE_MAX_LIKE_PATTERN_LENGTH) { return; } + // See if we can find the current url in our history database and + // add the new title to it. if (url.startsWith("http://www.")) { url = url.substring(11); } else if (url.startsWith("http://")) { @@ -3537,15 +3707,12 @@ public class BrowserActivity extends Activity Cursor c = mResolver.query(Browser.BOOKMARKS_URI, Browser.HISTORY_PROJECTION, where, selArgs, null); if (c.moveToFirst()) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "updating cursor"); - } // Current implementation of database only has one entry per // url. - int titleIndex = - c.getColumnIndex(Browser.BookmarkColumns.TITLE); - c.updateString(titleIndex, title); - c.commitUpdates(); + ContentValues map = new ContentValues(); + map.put(Browser.BookmarkColumns.TITLE, title); + mResolver.update(Browser.BOOKMARKS_URI, map, + "_id = " + c.getInt(0), null); } c.close(); } catch (IllegalStateException e) { @@ -3559,6 +3726,81 @@ public class BrowserActivity extends Activity public void onReceivedIcon(WebView view, Bitmap icon) { updateIcon(view.getUrl(), icon); } + + @Override + public void onShowCustomView(View view) { + if (mCustomView != null) + return; + + // Add the custom view to its container. + mCustomViewContainer.addView(view, COVER_SCREEN_GRAVITY_CENTER); + mCustomView = view; + // Save the menu state and set it to empty while the custom + // view is showing. + mOldMenuState = mMenuState; + mMenuState = EMPTY_MENU; + // Finally show the custom view container. + mCustomViewContainer.setVisibility(View.VISIBLE); + mCustomViewContainer.bringToFront(); + } + + @Override + public void onHideCustomView() { + if (mCustomView == null) + return; + + // Remove the custom view from its container. + mCustomViewContainer.removeView(mCustomView); + mCustomView = null; + // Reset the old menu state. + mMenuState = mOldMenuState; + mOldMenuState = EMPTY_MENU; + mCustomViewContainer.setVisibility(View.GONE); + } + + /** + * The origin has exceeded it's database quota. + * @param url the URL that exceeded the quota + * @param databaseIdentifier the identifier of the database on + * which the transaction that caused the quota overflow was run + * @param currentQuota the current quota for the origin. + * @param quotaUpdater The callback to run when a decision to allow or + * deny quota has been made. Don't forget to call this! + */ + @Override + public void onExceededDatabaseQuota(String url, + String databaseIdentifier, long currentQuota, + WebStorage.QuotaUpdater quotaUpdater) { + if(LOGV_ENABLED) { + Log.v(LOGTAG, + "BrowserActivity received onExceededDatabaseQuota for " + + url + + ":" + + databaseIdentifier + + "(current quota: " + + currentQuota + + ")"); + } + mWebStorageQuotaUpdater = quotaUpdater; + String DIALOG_PACKAGE = "com.android.browser"; + String DIALOG_CLASS = DIALOG_PACKAGE + ".PermissionDialog"; + Intent intent = new Intent(); + intent.setClassName(DIALOG_PACKAGE, DIALOG_CLASS); + intent.putExtra(PermissionDialog.PARAM_ORIGIN, url); + intent.putExtra(PermissionDialog.PARAM_QUOTA, currentQuota); + startActivityForResult(intent, WEBSTORAGE_QUOTA_DIALOG); + } + + /* Adds a JavaScript error message to the system log. + * @param message The error message to report. + * @param lineNumber The line number of the error. + * @param sourceID The name of the source file that caused the error. + */ + @Override + public void addMessageToConsole(String message, int lineNumber, String sourceID) { + Log.w(LOGTAG, "Console: " + message + " (" + sourceID + ":" + lineNumber + ")"); + } + }; /** @@ -3674,19 +3916,19 @@ public class BrowserActivity extends Activity String cookies = CookieManager.getInstance().getCookie(url); ContentValues values = new ContentValues(); - values.put(Downloads.URI, uri.toString()); - values.put(Downloads.COOKIE_DATA, cookies); - values.put(Downloads.USER_AGENT, userAgent); - values.put(Downloads.NOTIFICATION_PACKAGE, + values.put(Downloads.COLUMN_URI, uri.toString()); + values.put(Downloads.COLUMN_COOKIE_DATA, cookies); + values.put(Downloads.COLUMN_USER_AGENT, userAgent); + values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, getPackageName()); - values.put(Downloads.NOTIFICATION_CLASS, + values.put(Downloads.COLUMN_NOTIFICATION_CLASS, BrowserDownloadPage.class.getCanonicalName()); - values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - values.put(Downloads.MIMETYPE, mimetype); - values.put(Downloads.FILENAME_HINT, filename); - values.put(Downloads.DESCRIPTION, uri.getHost()); + values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + values.put(Downloads.COLUMN_MIME_TYPE, mimetype); + values.put(Downloads.COLUMN_FILE_NAME_HINT, filename); + values.put(Downloads.COLUMN_DESCRIPTION, uri.getHost()); if (contentLength > 0) { - values.put(Downloads.TOTAL_BYTES, contentLength); + values.put(Downloads.COLUMN_TOTAL_BYTES, contentLength); } if (mimetype == null) { // We must have long pressed on a link or image to download it. We @@ -3752,7 +3994,11 @@ public class BrowserActivity extends Activity // If the tab overview is animating or being shown, do not update the // lock icon. if (mAnimationCount == 0 && mTabOverview == null) { - getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d); + if (CUSTOM_BROWSER_BAR) { + mTitleBar.setLock(d); + } else { + getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, d); + } } } @@ -4253,8 +4499,8 @@ public class BrowserActivity extends Activity // middle of an animation, animate away from it to the // current tab. if (mTabOverview != null && mAnimationCount == 0) { - sendAnimateFromOverview(currentTab, false, new UrlData(data), - TAB_OVERVIEW_DELAY, null); + sendAnimateFromOverview(currentTab, false, + new UrlData(data), TAB_OVERVIEW_DELAY, null); } else { dismissSubWindow(currentTab); if (data != null && data.length() != 0) { @@ -4264,6 +4510,14 @@ public class BrowserActivity extends Activity } } break; + case WEBSTORAGE_QUOTA_DIALOG: + long currentQuota = 0; + if (resultCode == RESULT_OK && intent != null) { + currentQuota = intent.getLongExtra( + PermissionDialog.PARAM_QUOTA, currentQuota); + } + mWebStorageQuotaUpdater.updateQuota(currentQuota); + break; default: break; } @@ -4304,8 +4558,8 @@ public class BrowserActivity extends Activity // was clicked on. if (mTabControl.getTabCount() == 0) { current = mTabControl.createNewTab(); - sendAnimateFromOverview(current, true, - new UrlData(mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null); + sendAnimateFromOverview(current, true, new UrlData( + mSettings.getHomePage()), TAB_OVERVIEW_DELAY, null); } else { final int index = position > 0 ? (position - 1) : 0; current = mTabControl.getTab(index); @@ -4341,8 +4595,8 @@ public class BrowserActivity extends Activity if (index == ImageGrid.NEW_TAB) { openTabAndShow(mSettings.getHomePage(), null, false, null); } else { - sendAnimateFromOverview(mTabControl.getTab(index), - false, EMPTY_URL_DATA, 0, null); + sendAnimateFromOverview(mTabControl.getTab(index), false, + EMPTY_URL_DATA, 0, null); } } } @@ -4364,13 +4618,19 @@ public class BrowserActivity extends Activity AnimatingView(Context ctxt, TabControl.Tab t) { super(ctxt); mTab = t; - // Use the top window in the animation since the tab overview will - // display the top window in each cell. - final WebView w = t.getTopWindow(); - mPicture = w.capturePicture(); - mScale = w.getScale() / w.getWidth(); - mScrollX = w.getScrollX(); - mScrollY = w.getScrollY(); + if (t != null && t.getTopWindow() != null) { + // Use the top window in the animation since the tab overview + // will display the top window in each cell. + final WebView w = t.getTopWindow(); + mPicture = w.capturePicture(); + mScale = w.getScale() / w.getWidth(); + mScrollX = w.getScrollX(); + mScrollY = w.getScrollY(); + } else { + mPicture = null; + mScale = 1.0f; + mScrollX = mScrollY = 0; + } } @Override @@ -4444,16 +4704,20 @@ public class BrowserActivity extends Activity mAnimationCount++; // Always change the title bar to the window overview title while // animating. - getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null); - getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null); - getWindow().setFeatureInt(Window.FEATURE_PROGRESS, - Window.PROGRESS_VISIBILITY_OFF); - setTitle(R.string.tab_picker_title); + if (CUSTOM_BROWSER_BAR) { + mTitleBar.setToTabPicker(); + } else { + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, null); + getWindow().setFeatureDrawable(Window.FEATURE_RIGHT_ICON, null); + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + Window.PROGRESS_VISIBILITY_OFF); + setTitle(R.string.tab_picker_title); + } // Make the menu empty until the animation completes. mMenuState = EMPTY_MENU; } - private void bookmarksOrHistoryPicker(boolean startWithHistory) { + /* package */ void bookmarksOrHistoryPicker(boolean startWithHistory) { WebView current = mTabControl.getCurrentWebView(); if (current == null) { return; @@ -4529,7 +4793,7 @@ public class BrowserActivity extends Activity return 0; } - static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( + protected static final Pattern ACCEPTED_URI_SCHEMA = Pattern.compile( "(?i)" + // switch on case insensitive matching "(" + // begin group for schema "(?:http|https|file):\\/\\/" + @@ -4606,11 +4870,14 @@ public class BrowserActivity extends Activity private ContentResolver mResolver; private FrameLayout mContentView; private ImageGrid mTabOverview; + private View mCustomView; + private FrameLayout mCustomViewContainer; // FIXME, temp address onPrepareMenu performance problem. When we move everything out of // view, we should rewrite this. private int mCurrentMenuState = 0; private int mMenuState = R.id.MAIN_MENU; + private int mOldMenuState = EMPTY_MENU; private static final int EMPTY_MENU = -1; private Menu mMenu; @@ -4701,6 +4968,11 @@ public class BrowserActivity extends Activity new FrameLayout.LayoutParams( ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); + /*package*/ static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER = + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, + Gravity.CENTER); // Google search final static String QuickSearch_G = "http://www.google.com/m?q=%s"; // Wikipedia search @@ -4734,6 +5006,8 @@ public class BrowserActivity extends Activity private Toast mStopToast; + private TitleBar mTitleBar; + // Used during animations to prevent other animations from being triggered. // A count is used since the animation to and from the Window overview can // overlap. A count of 0 means no animation where a count of > 0 means @@ -4751,10 +5025,13 @@ public class BrowserActivity extends Activity private IntentFilter mNetworkStateChangedFilter; private BroadcastReceiver mNetworkStateIntentReceiver; + private BroadcastReceiver mPackageInstallationReceiver; + // activity requestCode - final static int COMBO_PAGE = 1; - final static int DOWNLOAD_PAGE = 2; - final static int PREFERENCES_PAGE = 3; + final static int COMBO_PAGE = 1; + final static int DOWNLOAD_PAGE = 2; + final static int PREFERENCES_PAGE = 3; + final static int WEBSTORAGE_QUOTA_DIALOG = 4; // the frenquency of checking whether system memory is low final static int CHECK_MEMORY_INTERVAL = 30000; // 30 seconds diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java index 27782e0..c403b44 100644 --- a/src/com/android/browser/BrowserBookmarksAdapter.java +++ b/src/com/android/browser/BrowserBookmarksAdapter.java @@ -40,8 +40,6 @@ import java.io.ByteArrayOutputStream; class BrowserBookmarksAdapter extends BaseAdapter { - private final String LOGTAG = "Bookmarks"; - private String mCurrentPage; private Cursor mCursor; private int mCount; @@ -181,18 +179,7 @@ class BrowserBookmarksAdapter extends BaseAdapter { } mCursor.moveToPosition(position- mExtraOffset); String url = mCursor.getString(Browser.HISTORY_PROJECTION_URL_INDEX); - WebIconDatabase.getInstance().releaseIconForPageUrl(url); - Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, mCursor - .getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); - int numVisits = mCursor.getInt(Browser.HISTORY_PROJECTION_VISITS_INDEX); - if (0 == numVisits) { - mContentResolver.delete(uri, null, null); - } else { - // It is no longer a bookmark, but it is still a visited site. - ContentValues values = new ContentValues(); - values.put(Browser.BookmarkColumns.BOOKMARK, 0); - mContentResolver.update(uri, values, null, null); - } + Bookmarks.removeFromBookmarks(null, mContentResolver, url); refreshList(); } diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java index dd34c14..45fca87 100644 --- a/src/com/android/browser/BrowserBookmarksPage.java +++ b/src/com/android/browser/BrowserBookmarksPage.java @@ -44,6 +44,7 @@ import android.view.ViewGroup; import android.view.ContextMenu.ContextMenuInfo; import android.widget.AdapterView; import android.widget.ListView; +import android.widget.Toast; /** * View showing the user's bookmarks in the browser. @@ -107,7 +108,13 @@ public class BrowserBookmarksPage extends Activity implements break; case R.id.copy_url_context_menu_id: copy(getUrl(i.position)); - + break; + case R.id.homepage_context_menu_id: + BrowserSettings.getInstance().setHomePage(this, + getUrl(i.position)); + Toast.makeText(this, R.string.homepage_set, + Toast.LENGTH_LONG).show(); + break; default: return super.onContextItemSelected(item); } diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java index 38b83fe..16cb982 100644 --- a/src/com/android/browser/BrowserDownloadAdapter.java +++ b/src/com/android/browser/BrowserDownloadAdapter.java @@ -60,14 +60,14 @@ public class BrowserDownloadAdapter extends ResourceCursorAdapter { public BrowserDownloadAdapter(Context context, int layout, Cursor c) { super(context, layout, c); mFilenameColumnId = c.getColumnIndexOrThrow(Downloads._DATA); - mTitleColumnId = c.getColumnIndexOrThrow(Downloads.TITLE); - mDescColumnId = c.getColumnIndexOrThrow(Downloads.DESCRIPTION); - mStatusColumnId = c.getColumnIndexOrThrow(Downloads.STATUS); - mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.TOTAL_BYTES); + mTitleColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TITLE); + mDescColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_DESCRIPTION); + mStatusColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_STATUS); + mTotalBytesColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES); mCurrentBytesColumnId = - c.getColumnIndexOrThrow(Downloads.CURRENT_BYTES); - mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.MIMETYPE); - mDateColumnId = c.getColumnIndexOrThrow(Downloads.LAST_MODIFICATION); + c.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES); + mMimetypeColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE); + mDateColumnId = c.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION); } @Override @@ -106,7 +106,7 @@ public class BrowserDownloadAdapter extends ResourceCursorAdapter { // We have a filename, so we can build a title from that title = new File(fullFilename).getName(); ContentValues values = new ContentValues(); - values.put(Downloads.TITLE, title); + values.put(Downloads.COLUMN_TITLE, title); // assume "_id" is the first column for the cursor context.getContentResolver().update( ContentUris.withAppendedId(Downloads.CONTENT_URI, diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java index 4397337..22e0e65 100644 --- a/src/com/android/browser/BrowserDownloadPage.java +++ b/src/com/android/browser/BrowserDownloadPage.java @@ -66,34 +66,30 @@ public class BrowserDownloadPage extends Activity setTitle(getText(R.string.download_title)); mListView = (ListView) findViewById(R.id.list); - LayoutInflater factory = LayoutInflater.from(this); - View v = factory.inflate(R.layout.no_downloads, null); - addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT, - LayoutParams.FILL_PARENT)); - mListView.setEmptyView(v); + mListView.setEmptyView(findViewById(R.id.empty)); mDownloadCursor = managedQuery(Downloads.CONTENT_URI, - new String [] {"_id", Downloads.TITLE, Downloads.STATUS, - Downloads.TOTAL_BYTES, Downloads.CURRENT_BYTES, - Downloads._DATA, Downloads.DESCRIPTION, - Downloads.MIMETYPE, Downloads.LAST_MODIFICATION, - Downloads.VISIBILITY}, + new String [] {"_id", Downloads.COLUMN_TITLE, Downloads.COLUMN_STATUS, + Downloads.COLUMN_TOTAL_BYTES, Downloads.COLUMN_CURRENT_BYTES, + Downloads._DATA, Downloads.COLUMN_DESCRIPTION, + Downloads.COLUMN_MIME_TYPE, Downloads.COLUMN_LAST_MODIFICATION, + Downloads.COLUMN_VISIBILITY}, null, null); // only attach everything to the listbox if we can access // the download database. Otherwise, just show it empty if (mDownloadCursor != null) { mStatusColumnId = - mDownloadCursor.getColumnIndexOrThrow(Downloads.STATUS); + mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS); mIdColumnId = mDownloadCursor.getColumnIndexOrThrow(Downloads._ID); mTitleColumnId = - mDownloadCursor.getColumnIndexOrThrow(Downloads.TITLE); + mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_TITLE); // Create a list "controller" for the data mDownloadAdapter = new BrowserDownloadAdapter(this, R.layout.browser_download_item, mDownloadCursor); - + mListView.setAdapter(mDownloadAdapter); mListView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); mListView.setOnCreateContextMenuListener(this); @@ -403,7 +399,7 @@ public class BrowserDownloadPage extends Activity mDownloadCursor.getColumnIndexOrThrow(Downloads._DATA); String filename = mDownloadCursor.getString(filenameColumnId); int mimetypeColumnId = - mDownloadCursor.getColumnIndexOrThrow(Downloads.MIMETYPE); + mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE); String mimetype = mDownloadCursor.getString(mimetypeColumnId); Uri path = Uri.parse(filename); // If there is no scheme, then it must be a file @@ -453,13 +449,13 @@ public class BrowserDownloadPage extends Activity private void hideCompletedDownload() { int status = mDownloadCursor.getInt(mStatusColumnId); - int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.VISIBILITY); + int visibilityColumn = mDownloadCursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY); int visibility = mDownloadCursor.getInt(visibilityColumn); if (Downloads.isStatusCompleted(status) && visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { ContentValues values = new ContentValues(); - values.put(Downloads.VISIBILITY, Downloads.VISIBILITY_VISIBLE); + values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_VISIBLE); getContentResolver().update( ContentUris.withAppendedId(Downloads.CONTENT_URI, mDownloadCursor.getLong(mIdColumnId)), values, null, null); diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java index 42ca848..e333416 100644 --- a/src/com/android/browser/BrowserHistoryPage.java +++ b/src/com/android/browser/BrowserHistoryPage.java @@ -41,6 +41,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ContextMenu.ContextMenuInfo; +import android.view.ViewStub; import android.webkit.DateSorter; import android.webkit.WebIconDatabase.IconListener; import android.widget.AdapterView; @@ -48,6 +49,7 @@ import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.TextView; +import android.widget.Toast; import java.util.List; import java.util.Vector; @@ -110,8 +112,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { setListAdapter(mAdapter); final ExpandableListView list = getExpandableListView(); list.setOnCreateContextMenuListener(this); - LayoutInflater factory = LayoutInflater.from(this); - View v = factory.inflate(R.layout.empty_history, null); + View v = new ViewStub(this, R.layout.empty_history); addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); list.setEmptyView(v); @@ -222,6 +223,11 @@ public class BrowserHistoryPage extends ExpandableListActivity { Browser.deleteFromHistory(getContentResolver(), url); mAdapter.refreshData(); return true; + case R.id.homepage_context_menu_id: + BrowserSettings.getInstance().setHomePage(this, url); + Toast.makeText(this, R.string.homepage_set, + Toast.LENGTH_LONG).show(); + return true; default: break; } @@ -267,18 +273,25 @@ public class BrowserHistoryPage extends ExpandableListActivity { // Array for each of our bins. Each entry represents how many items are // in that bin. - int mItemMap[]; + private int mItemMap[]; // This is our GroupCount. We will have at most DateSorter.DAY_COUNT // bins, less if the user has no items in one or more bins. - int mNumberOfBins; - Vector<DataSetObserver> mObservers; - Cursor mCursor; + private int mNumberOfBins; + private Vector<DataSetObserver> mObservers; + private Cursor mCursor; HistoryAdapter() { mObservers = new Vector<DataSetObserver>(); - String whereClause = Browser.BookmarkColumns.VISITS + " > 0 "; - String orderBy = Browser.BookmarkColumns.DATE + " DESC"; + final String whereClause = Browser.BookmarkColumns.VISITS + " > 0" + // In AddBookmarkPage, where we save new bookmarks, we add + // three visits to newly created bookmarks, so that + // bookmarks that have not been visited will show up in the + // most visited, and higher in the goto search box. + // However, this puts the site in the history, unless we + // ignore sites with a DATE of 0, which the next line does. + + " AND " + Browser.BookmarkColumns.DATE + " > 0"; + final String orderBy = Browser.BookmarkColumns.DATE + " DESC"; mCursor = managedQuery( Browser.BOOKMARKS_URI, diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java index d4708c3..7324f24 100644 --- a/src/com/android/browser/BrowserHomepagePreference.java +++ b/src/com/android/browser/BrowserHomepagePreference.java @@ -50,8 +50,8 @@ public class BrowserHomepagePreference extends EditTextPreference implements if (dialog != null) { String url = s.toString(); dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled( - url.length() == 0 || url.equals("about:blank") || - Regex.WEB_URL_PATTERN.matcher(url).matches()); + url.length() == 0 || + BrowserActivity.ACCEPTED_URI_SCHEMA.matcher(url).matches()); } } diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java index 3b747d1..2524eb8 100644 --- a/src/com/android/browser/BrowserPreferencesPage.java +++ b/src/com/android/browser/BrowserPreferencesPage.java @@ -17,12 +17,19 @@ package com.android.browser; import java.util.List; +import java.util.Vector; +import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; +import android.util.Log; +import android.webkit.Plugin; +import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.Plugin; @@ -30,6 +37,8 @@ public class BrowserPreferencesPage extends PreferenceActivity implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { + String TAG = "BrowserPreferencesPage"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -66,6 +75,28 @@ public class BrowserPreferencesPage extends PreferenceActivity e = findPreference(BrowserSettings.PREF_GEARS_SETTINGS); e.setOnPreferenceClickListener(this); + + PreferenceScreen manageDatabases = (PreferenceScreen) + findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS); + Intent intent = new Intent(this, WebsiteSettingsActivity.class); + manageDatabases.setIntent(intent); + } + + /* + * We need to set the manageDatabases PreferenceScreen state + * in the onResume(), as the number of origins with databases + * could have changed after calling the WebsiteSettingsActivity. + */ + @Override + protected void onResume() { + super.onResume(); + PreferenceScreen manageDatabases = (PreferenceScreen) + findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS); + manageDatabases.setEnabled(false); + Vector origins = WebStorage.getInstance().getOrigins(); + if ((origins != null) && (origins.size() > 0)) { + manageDatabases.setEnabled(true); + } } @Override diff --git a/src/com/android/browser/BrowserProvider.java b/src/com/android/browser/BrowserProvider.java index bc12bde..c1d2ef7 100644 --- a/src/com/android/browser/BrowserProvider.java +++ b/src/com/android/browser/BrowserProvider.java @@ -96,6 +96,8 @@ public class BrowserProvider extends ContentProvider { private static final int MAX_SUGGESTION_SHORT_ENTRIES = 3; private static final int MAX_SUGGESTION_LONG_ENTRIES = 6; + private static final String MAX_SUGGESTION_LONG_ENTRIES_STRING = + Integer.valueOf(MAX_SUGGESTION_LONG_ENTRIES).toString(); // make sure that these match the index of TABLE_NAMES private static final int URI_MATCH_BOOKMARKS = 0; @@ -143,7 +145,8 @@ public class BrowserProvider extends ContentProvider { // 15 -> 17 Set it up for the SearchManager // 17 -> 18 Added favicon in bookmarks table for Home shortcuts // 18 -> 19 Remove labels table - private static final int DATABASE_VERSION = 19; + // 19 -> 20 Added thumbnail + private static final int DATABASE_VERSION = 20; // Regular expression which matches http://, followed by some stuff, followed by // optionally a trailing slash, all matched as separate groups. @@ -215,7 +218,8 @@ public class BrowserProvider extends ContentProvider { "created LONG," + "description TEXT," + "bookmark INTEGER," + - "favicon BLOB DEFAULT NULL" + + "favicon BLOB DEFAULT NULL," + + "thumbnail BLOB DEFAULT NULL" + ");"); final CharSequence[] bookmarks = mContext.getResources() @@ -242,9 +246,12 @@ public class BrowserProvider extends ContentProvider { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(TAG, "Upgrading database from version " + oldVersion + " to " - + newVersion + ", which will destroy all old data"); + + newVersion); if (oldVersion == 18) { db.execSQL("DROP TABLE IF EXISTS labels"); + } + if (oldVersion <= 19) { + db.execSQL("ALTER TABLE bookmarks ADD COLUMN thumbnail BLOB DEFAULT NULL;"); } else { db.execSQL("DROP TABLE IF EXISTS bookmarks"); db.execSQL("DROP TABLE IF EXISTS searches"); @@ -471,22 +478,22 @@ public class BrowserProvider extends ContentProvider { case SUGGEST_COLUMN_ICON_1_ID: if (mHistoryCount > mPos) { if (mHistoryCursor.getInt(3) == 1) { - return new Integer( + return Integer.valueOf( R.drawable.ic_search_category_bookmark) .toString(); } else { - return new Integer( + return Integer.valueOf( R.drawable.ic_search_category_history) .toString(); } } else { - return new Integer( + return Integer.valueOf( R.drawable.ic_search_category_suggest) .toString(); } case SUGGEST_COLUMN_ICON_2_ID: - return new String("0"); + return "0"; case SUGGEST_COLUMN_QUERY_ID: if (mHistoryCount > mPos) { @@ -641,7 +648,8 @@ public class BrowserProvider extends ContentProvider { myArgs = null; } else { String like = selectionArgs[0] + "%"; - if (selectionArgs[0].startsWith("http")) { + if (selectionArgs[0].startsWith("http") + || selectionArgs[0].startsWith("file")) { myArgs = new String[1]; myArgs[0] = like; suggestSelection = selection; @@ -659,8 +667,7 @@ public class BrowserProvider extends ContentProvider { Cursor c = db.query(TABLE_NAMES[URI_MATCH_BOOKMARKS], SUGGEST_PROJECTION, suggestSelection, myArgs, null, null, - ORDER_BY, - (new Integer(MAX_SUGGESTION_LONG_ENTRIES)).toString()); + ORDER_BY, MAX_SUGGESTION_LONG_ENTRIES_STRING); if (match == URI_MATCH_BOOKMARKS_SUGGEST || Regex.WEB_URL_PATTERN.matcher(selectionArgs[0]).matches()) { diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index a5e23c9..aa7e103 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -20,13 +20,17 @@ import com.google.android.providers.GoogleSettings.Partner; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.ActivityInfo; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; import android.webkit.CookieManager; import android.webkit.WebView; import android.webkit.WebViewDatabase; import android.webkit.WebIconDatabase; import android.webkit.WebSettings; +import android.webkit.WebStorage; import android.preference.PreferenceManager; import android.provider.Browser; @@ -48,7 +52,7 @@ import java.util.Observable; */ class BrowserSettings extends Observable { - // Public variables for settings + // Private variables for settings // NOTE: these defaults need to be kept in sync with the XML // until the performance of PreferenceManager.setDefaultValues() // is improved. @@ -65,7 +69,18 @@ class BrowserSettings extends Observable { private String homeUrl = ""; private boolean loginInitialized = false; private boolean autoFitPage = true; + private boolean landscapeOnly = false; private boolean showDebugSettings = false; + private String databasePath; // default value set in loadFromDb() + private boolean databaseEnabled = true; + private long webStorageDefaultQuota = 5 * 1024 * 1024; + // The Browser always enables Application Caches. + private boolean appCacheEnabled = true; + private String appCachePath; // default value set in loadFromDb(). + private boolean domStorageEnabled = true; + private String jsFlags = ""; + + private final static String TAG = "BrowserSettings"; // Development settings public WebSettings.LayoutAlgorithm layoutAlgorithm = @@ -75,6 +90,7 @@ class BrowserSettings extends Observable { private boolean tracing = false; private boolean lightTouch = false; private boolean navDump = false; + // Browser only settings private boolean doFlick = false; @@ -97,22 +113,25 @@ class BrowserSettings extends Observable { "privacy_clear_form_data"; public final static String PREF_CLEAR_PASSWORDS = "privacy_clear_passwords"; + public final static String PREF_DEFAULT_QUOTA = + "webstorage_default_quota"; public final static String PREF_EXTRAS_RESET_DEFAULTS = "reset_default_preferences"; public final static String PREF_DEBUG_SETTINGS = "debug_menu"; public final static String PREF_GEARS_SETTINGS = "gears_settings"; + public final static String PREF_WEBSITE_SETTINGS = "website_settings"; public final static String PREF_TEXT_SIZE = "text_size"; public final static String PREF_DEFAULT_ZOOM = "default_zoom"; public final static String PREF_DEFAULT_TEXT_ENCODING = "default_text_encoding"; private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (Macintosh; " + - "U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.18 (KHTML, " + - "like Gecko) Version/3.1.2 Safari/525.20.1"; + "U; Intel Mac OS X 10_5_7; en-us) AppleWebKit/530.17 (KHTML, " + + "like Gecko) Version/4.0 Safari/530.17"; private static final String IPHONE_USERAGENT = "Mozilla/5.0 (iPhone; U; " + - "CPU iPhone OS 2_2 like Mac OS X; en-us) AppleWebKit/525.18.1 " + - "(KHTML, like Gecko) Version/3.1.1 Mobile/5G77 Safari/525.20"; + "CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 " + + "(KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16"; // Value to truncate strings when adding them to a TextView within // a ListView @@ -157,7 +176,6 @@ class BrowserSettings extends Observable { s.setLoadsImagesAutomatically(b.loadsImagesAutomatically); s.setJavaScriptEnabled(b.javaScriptEnabled); s.setPluginsEnabled(b.pluginsEnabled); - s.setPluginsPath(b.pluginsPath); s.setJavaScriptCanOpenWindowsAutomatically( b.javaScriptCanOpenWindowsAutomatically); s.setDefaultTextEncodingName(b.defaultTextEncodingName); @@ -178,6 +196,15 @@ class BrowserSettings extends Observable { s.setSupportMultipleWindows(true); // Turn off file access s.setAllowFileAccess(false); + + s.setDatabasePath(b.databasePath); + s.setDatabaseEnabled(b.databaseEnabled); + s.setDomStorageEnabled(b.domStorageEnabled); + s.setWebStorageDefaultQuota(b.webStorageDefaultQuota); + + // Turn on Application Caches. + s.setAppCachePath(b.appCachePath); + s.setAppCacheEnabled(b.appCacheEnabled); } } @@ -197,6 +224,10 @@ class BrowserSettings extends Observable { // Set the default value for the plugins path to the application's // local directory. pluginsPath = ctx.getDir("plugins", 0).getPath(); + // Set the default value for the Application Caches path. + appCachePath = ctx.getDir("appcache", 0).getPath(); + // Set the default value for the Database path. + databasePath = ctx.getDir("databases", 0).getPath(); homeUrl = getFactoryResetHomeUrl(ctx); @@ -219,6 +250,15 @@ class BrowserSettings extends Observable { pluginsEnabled = p.getBoolean("enable_plugins", pluginsEnabled); pluginsPath = p.getString("plugins_path", pluginsPath); + databasePath = p.getString("database_path", databasePath); + databaseEnabled = p.getBoolean("enable_database", databaseEnabled); + webStorageDefaultQuota = Long.parseLong(p.getString(PREF_DEFAULT_QUOTA, + String.valueOf(webStorageDefaultQuota))); + appCacheEnabled = p.getBoolean("enable_appcache", + appCacheEnabled); + domStorageEnabled = p.getBoolean("enable_domstorage", + domStorageEnabled); + appCachePath = p.getString("appcache_path", appCachePath); javaScriptCanOpenWindowsAutomatically = !p.getBoolean( "block_popup_windows", !javaScriptCanOpenWindowsAutomatically); @@ -238,6 +278,14 @@ class BrowserSettings extends Observable { zoomDensity = WebSettings.ZoomDensity.valueOf( p.getString(PREF_DEFAULT_ZOOM, zoomDensity.name())); autoFitPage = p.getBoolean("autofit_pages", autoFitPage); + boolean landscapeOnlyTemp = + p.getBoolean("landscape_only", landscapeOnly); + if (landscapeOnlyTemp != landscapeOnly) { + landscapeOnly = landscapeOnlyTemp; + mTabControl.getBrowserActivity().setRequestedOrientation( + landscapeOnly ? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); + } useWideViewPort = true; // use wide view port for either setting if (autoFitPage) { layoutAlgorithm = WebSettings.LayoutAlgorithm.NARROW_COLUMNS; @@ -274,7 +322,9 @@ class BrowserSettings extends Observable { doFlick = p.getBoolean("enable_flick", doFlick); userAgent = Integer.parseInt(p.getString("user_agent", "0")); } - update(); + // JS flags is loaded from DB even if showDebugSettings is false, + // so that it can be set once and be effective all the time. + jsFlags = p.getString("js_engine_flags", ""); } public String getPluginsPath() { @@ -285,6 +335,10 @@ class BrowserSettings extends Observable { return homeUrl; } + public String getJsFlags() { + return jsFlags; + } + public void setHomePage(Context context, String url) { Editor ed = PreferenceManager. getDefaultSharedPreferences(context).edit(); @@ -436,14 +490,24 @@ class BrowserSettings extends Observable { db.clearHttpAuthUsernamePassword(); } - /*package*/ void resetDefaultPreferences(Context context) { + /*package*/ void clearDatabases(Context context) { + WebStorage.getInstance().deleteAllDatabases(); + // Remove all listed databases from the preferences + PreferenceActivity activity = (PreferenceActivity) context; + PreferenceScreen screen = (PreferenceScreen) + activity.findPreference(BrowserSettings.PREF_WEBSITE_SETTINGS); + screen.removeAll(); + screen.setEnabled(false); + } + + /*package*/ void resetDefaultPreferences(Context ctx) { SharedPreferences p = - PreferenceManager.getDefaultSharedPreferences(context); + PreferenceManager.getDefaultSharedPreferences(ctx); p.edit().clear().commit(); - PreferenceManager.setDefaultValues(context, R.xml.browser_preferences, + PreferenceManager.setDefaultValues(ctx, R.xml.browser_preferences, true); // reset homeUrl - setHomePage(context, getFactoryResetHomeUrl(context)); + setHomePage(ctx, getFactoryResetHomeUrl(ctx)); } private String getFactoryResetHomeUrl(Context context) { diff --git a/src/com/android/browser/BrowserYesNoPreference.java b/src/com/android/browser/BrowserYesNoPreference.java index 65cde71..ae93882 100644 --- a/src/com/android/browser/BrowserYesNoPreference.java +++ b/src/com/android/browser/BrowserYesNoPreference.java @@ -38,6 +38,7 @@ class BrowserYesNoPreference extends YesNoPreference { Context context = getContext(); if (BrowserSettings.PREF_CLEAR_CACHE.equals(getKey())) { BrowserSettings.getInstance().clearCache(context); + BrowserSettings.getInstance().clearDatabases(context); } else if (BrowserSettings.PREF_CLEAR_COOKIES.equals(getKey())) { BrowserSettings.getInstance().clearCookies(context); } else if (BrowserSettings.PREF_CLEAR_HISTORY.equals(getKey())) { diff --git a/src/com/android/browser/CombinedBookmarkHistoryActivity.java b/src/com/android/browser/CombinedBookmarkHistoryActivity.java index 963f179..6926c8f 100644 --- a/src/com/android/browser/CombinedBookmarkHistoryActivity.java +++ b/src/com/android/browser/CombinedBookmarkHistoryActivity.java @@ -84,7 +84,7 @@ public class CombinedBookmarkHistoryActivity extends TabActivity Resources resources = getResources(); getIconListenerSet(getContentResolver()); - Intent bookmarksIntent = new Intent(this, BrowserBookmarksPage.class); + Intent bookmarksIntent = new Intent(this, BookmarkGridPage.class); bookmarksIntent.putExtras(extras); tabHost.addTab(tabHost.newTabSpec(BOOKMARKS_TAB) .setIndicator(resources.getString(R.string.tab_bookmarks), diff --git a/src/com/android/browser/FetchUrlMimeType.java b/src/com/android/browser/FetchUrlMimeType.java index 8578643..c585dbb 100644 --- a/src/com/android/browser/FetchUrlMimeType.java +++ b/src/com/android/browser/FetchUrlMimeType.java @@ -58,7 +58,7 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> { mValues = values[0]; // Check to make sure we have a URI to download - String uri = mValues.getAsString(Downloads.URI); + String uri = mValues.getAsString(Downloads.COLUMN_URI); if (uri == null || uri.length() == 0) { return null; } @@ -66,15 +66,15 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> { // User agent is likely to be null, though the AndroidHttpClient // seems ok with that. AndroidHttpClient client = AndroidHttpClient.newInstance( - mValues.getAsString(Downloads.USER_AGENT)); + mValues.getAsString(Downloads.COLUMN_USER_AGENT)); HttpHead request = new HttpHead(uri); - String cookie = mValues.getAsString(Downloads.COOKIE_DATA); + String cookie = mValues.getAsString(Downloads.COLUMN_COOKIE_DATA); if (cookie != null && cookie.length() > 0) { request.addHeader("Cookie", cookie); } - String referer = mValues.getAsString(Downloads.REFERER); + String referer = mValues.getAsString(Downloads.COLUMN_REFERER); if (referer != null && referer.length() > 0) { request.addHeader("Referer", referer); } @@ -111,19 +111,19 @@ class FetchUrlMimeType extends AsyncTask<ContentValues, String, String> { @Override public void onPostExecute(String mimeType) { if (mimeType != null) { - String url = mValues.getAsString(Downloads.URI); + String url = mValues.getAsString(Downloads.COLUMN_URI); if (mimeType.equalsIgnoreCase("text/plain") || mimeType.equalsIgnoreCase("application/octet-stream")) { String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( MimeTypeMap.getFileExtensionFromUrl(url)); if (newMimeType != null) { - mValues.put(Downloads.MIMETYPE, newMimeType); + mValues.put(Downloads.COLUMN_MIME_TYPE, newMimeType); } } String filename = URLUtil.guessFileName(url, null, mimeType); - mValues.put(Downloads.FILENAME_HINT, filename); + mValues.put(Downloads.COLUMN_FILE_NAME_HINT, filename); } // Start the download diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java index 6e9574c..2049bd0 100644 --- a/src/com/android/browser/FindDialog.java +++ b/src/com/android/browser/FindDialog.java @@ -42,7 +42,6 @@ import android.widget.TextView; private BrowserActivity mBrowserActivity; // Views with which the user can interact. - private View mOk; private EditText mEditText; private View mNextButton; private View mPrevButton; @@ -129,7 +128,6 @@ import android.widget.TextView; button = findViewById(R.id.done); button.setOnClickListener(mFindCancelListener); - mOk = button; mMatches = (TextView) findViewById(R.id.matches); mMatchesView = findViewById(R.id.matches_view); @@ -143,23 +141,14 @@ import android.widget.TextView; mBrowserActivity.closeFind(); mWebView.clearMatches(); } - + @Override public boolean dispatchKeyEvent(KeyEvent event) { - int code = event.getKeyCode(); - boolean up = event.getAction() == KeyEvent.ACTION_UP; - switch (code) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: - if (!mEditText.hasFocus()) { - break; - } - if (up) { - findNext(); - } - return true; - default: - break; + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER + && event.getAction() == KeyEvent.ACTION_UP + && mEditText.hasFocus()) { + findNext(); + return true; } return super.dispatchKeyEvent(event); } diff --git a/src/com/android/browser/HistoryItem.java b/src/com/android/browser/HistoryItem.java index 55e43f0..b37a3bd 100644 --- a/src/com/android/browser/HistoryItem.java +++ b/src/com/android/browser/HistoryItem.java @@ -17,23 +17,13 @@ package com.android.browser; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; import android.graphics.Bitmap; -import android.net.Uri; import android.provider.Browser; -import android.util.Log; import android.view.View; -import android.webkit.WebIconDatabase; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; -import android.widget.Toast; - -import java.util.Date; /** * Layout representing a history item in the classic history viewer. @@ -54,56 +44,13 @@ import java.util.Date; mListener = new CompoundButton.OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - ContentResolver cr = mContext.getContentResolver(); - Cursor cursor = cr.query( - Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, - "url = ?", - new String[] { mUrl }, - null); - boolean first = cursor.moveToFirst(); - // Should be in the database no matter what - if (!first) { - throw new AssertionError("URL is not in the database!"); - } if (isChecked) { - // Add to bookmarks - // FIXME: Share code with AddBookmarkPage.java - ContentValues map = new ContentValues(); - map.put(Browser.BookmarkColumns.CREATED, - new Date().getTime()); - map.put(Browser.BookmarkColumns.TITLE, getName()); - map.put(Browser.BookmarkColumns.BOOKMARK, 1); - try { - cr.update(Browser.BOOKMARKS_URI, map, - "_id = " + cursor.getInt(0), null); - } catch (IllegalStateException e) { - Log.e("HistoryItem", "no database!"); - } - WebIconDatabase.getInstance().retainIconForPageUrl(mUrl); - // catch IllegalStateException? - Toast.makeText(mContext, R.string.added_to_bookmarks, - Toast.LENGTH_LONG).show(); + Bookmarks.addBookmark(mContext, + mContext.getContentResolver(), mUrl, getName()); } else { - // Remove from bookmarks - // FIXME: This code should be shared with - // BrowserBookmarksAdapter.java - WebIconDatabase.getInstance().releaseIconForPageUrl(mUrl); - Uri uri = ContentUris.withAppendedId(Browser.BOOKMARKS_URI, - cursor.getInt(Browser.HISTORY_PROJECTION_ID_INDEX)); - // It is no longer a bookmark, but it is still a visited - // site. - ContentValues values = new ContentValues(); - values.put(Browser.BookmarkColumns.BOOKMARK, 0); - try { - cr.update(uri, values, null, null); - } catch (IllegalStateException e) { - Log.e("HistoryItem", "no database!"); - } - Toast.makeText(mContext, R.string.removed_from_bookmarks, - Toast.LENGTH_LONG).show(); + Bookmarks.removeFromBookmarks(mContext, + mContext.getContentResolver(), mUrl); } - cursor.deactivate(); } }; } diff --git a/src/com/android/browser/MostVisitedActivity.java b/src/com/android/browser/MostVisitedActivity.java index 704ee27..90052d3 100644 --- a/src/com/android/browser/MostVisitedActivity.java +++ b/src/com/android/browser/MostVisitedActivity.java @@ -35,6 +35,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.ViewStub; import java.util.Vector; @@ -50,8 +51,7 @@ public class MostVisitedActivity extends ListActivity { .addListener(new IconReceiver()); setListAdapter(mAdapter); ListView list = getListView(); - LayoutInflater factory = LayoutInflater.from(this); - View v = factory.inflate(R.layout.empty_history, null); + View v = new ViewStub(this, R.layout.empty_history); addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); list.setEmptyView(v); @@ -84,9 +84,9 @@ public class MostVisitedActivity extends ListActivity { private Vector<DataSetObserver> mObservers; private Cursor mCursor; // These correspond with projection below. - private final int mUrlIndex = 0; - private final int mTitleIndex = 1; - private final int mBookmarkIndex = 2; + private static final int mUrlIndex = 0; + private static final int mTitleIndex = 1; + private static final int mBookmarkIndex = 2; MyAdapter() { mObservers = new Vector<DataSetObserver>(); diff --git a/src/com/android/browser/PermissionDialog.java b/src/com/android/browser/PermissionDialog.java new file mode 100644 index 0000000..b71261a --- /dev/null +++ b/src/com/android/browser/PermissionDialog.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +/** + * Permission dialog for HTML5 + * @hide + */ +public class PermissionDialog extends Activity { + + private static final String TAG = "PermissionDialog"; + public static final String PARAM_ORIGIN = "origin"; + public static final String PARAM_QUOTA = "quota"; + + private String mWebStorageOrigin; + private long mWebStorageQuota = 0; + private int mNotification = 0; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + getParameters(); + setupDialog(); + } + + private void getParameters() { + Intent intent = getIntent(); + mWebStorageOrigin = intent.getStringExtra(PARAM_ORIGIN); + mWebStorageQuota = intent.getLongExtra(PARAM_QUOTA, 0); + } + + private void setupDialog() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.permission_dialog); + + setIcon(R.id.icon, android.R.drawable.ic_popup_disk_full); + setText(R.id.dialog_title, R.string.query_storage_quota_prompt); + setText(R.id.dialog_message, R.string.query_storage_quota_message); + setCharSequence(R.id.origin, mWebStorageOrigin); + + setupButton(R.id.button_allow, R.string.permission_button_allow, + new View.OnClickListener() { + public void onClick(View v) { allow(); } + }); + setupButton(R.id.button_alwaysdeny, R.string.permission_button_alwaysdeny, + new View.OnClickListener() { + public void onClick(View v) { alwaysdeny(); } + }); + setupButton(R.id.button_deny, R.string.permission_button_deny, + new View.OnClickListener() { + public void onClick(View v) { deny(); } + }); + } + + private void setText(int viewID, int stringID) { + setCharSequence(viewID, getString(stringID)); + } + + private void setCharSequence(int viewID, CharSequence string) { + View view = findViewById(viewID); + if (view == null) { + return; + } + view.setVisibility(View.VISIBLE); + TextView textView = (TextView) view; + textView.setText(string); + } + + private void setIcon(int viewID, int imageID) { + View view = findViewById(viewID); + if (view == null) { + return; + } + view.setVisibility(View.VISIBLE); + ImageView icon = (ImageView) view; + icon.setImageResource(imageID); + } + + private void setupButton(int viewID, int stringID, + View.OnClickListener listener) { + View view = findViewById(viewID); + if (view == null) { + return; + } + setText(viewID, stringID); + view.setOnClickListener(listener); + } + + private void useNextQuota() { + CharSequence[] values = getResources().getTextArray( + R.array.webstorage_quota_entries_values); + for (int i=0; i<values.length; i++) { + long value = Long.parseLong(values[i].toString()); + value *= (1024 * 1024); // the string array is expressed in MB + if (value > mWebStorageQuota) { + mWebStorageQuota = value; + break; + } + } + } + + private void allow() { + // If somehow there is no "next quota" in the ladder, + // we'll add 1MB anyway. + mWebStorageQuota += 1024*1024; + useNextQuota(); + mNotification = R.string.webstorage_notification; + closeDialog(); + } + + private void alwaysdeny() { + // Setting the quota to 0 will prevent any new data to be + // added, but the existing data will not be deleted. + mWebStorageQuota = 0; + mNotification = R.string.webstorage_notification; + closeDialog(); + } + + private void deny() { + closeDialog(); + } + + private void closeDialog() { + Intent intent = new Intent(); + intent.putExtra(PARAM_QUOTA, mWebStorageQuota); + setResult(RESULT_OK, intent); + showToast(); + finish(); + } + + private void showToast() { + if (mNotification != 0) { + Toast toast = Toast.makeText(this, mNotification, Toast.LENGTH_LONG); + toast.setGravity(Gravity.BOTTOM, 0, 0); + toast.show(); + } + } + + public boolean dispatchKeyEvent(KeyEvent event) { + if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK) + && (event.getAction() == KeyEvent.ACTION_DOWN)) { + closeDialog(); + return true; // event consumed + } + return super.dispatchKeyEvent(event); + } + +} diff --git a/src/com/android/browser/TabControl.java b/src/com/android/browser/TabControl.java index 575be8d..bdb57fa 100644 --- a/src/com/android/browser/TabControl.java +++ b/src/com/android/browser/TabControl.java @@ -446,6 +446,9 @@ class TabControl { * @return index of Tab or -1 if not found */ int getTabIndex(Tab tab) { + if (tab == null) { + return -1; + } return mTabs.indexOf(tab); } @@ -681,11 +684,11 @@ class TabControl { return; } - // free the WebView cache - Log.w(LOGTAG, "Free WebView cache"); + // free the WebView's unused memory (this includes the cache) + Log.w(LOGTAG, "Free WebView's unused memory and cache"); WebView view = getCurrentWebView(); if (view != null) { - view.clearCache(false); + view.freeMemory(); } // force a gc System.gc(); @@ -865,6 +868,48 @@ class TabControl { return setCurrentTab(newTab, false); } + /*package*/ void pauseCurrentTab() { + Tab t = getCurrentTab(); + if (t != null) { + t.mMainView.onPause(); + if (t.mSubView != null) { + t.mSubView.onPause(); + } + } + } + + /*package*/ void resumeCurrentTab() { + Tab t = getCurrentTab(); + if (t != null) { + t.mMainView.onResume(); + if (t.mSubView != null) { + t.mSubView.onResume(); + } + } + } + + private void putViewInForeground(WebView v, WebViewClient vc, + WebChromeClient cc) { + v.setWebViewClient(vc); + v.setWebChromeClient(cc); + v.setOnCreateContextMenuListener(mActivity); + v.setDownloadListener(mActivity); + v.onResume(); + } + + private void putViewInBackground(WebView v) { + // Set an empty callback so that default actions are not triggered. + v.setWebViewClient(mEmptyClient); + v.setWebChromeClient(mBackgroundChromeClient); + v.setOnCreateContextMenuListener(null); + // Leave the DownloadManager attached so that downloads can start in + // a non-active window. This can happen when going to a site that does + // a redirect after a period of time. The user could have switched to + // another tab while waiting for the download to start. + v.setDownloadListener(mActivity); + v.onPause(); + } + /** * If force is true, this method skips the check for newTab == current. */ @@ -890,7 +935,6 @@ class TabControl { mTabQueue.add(newTab); WebView mainView; - WebView subView; // Display the new current tab mCurrentTab = mTabs.indexOf(newTab); @@ -900,17 +944,12 @@ class TabControl { // Same work as in createNewTab() except don't do new Tab() newTab.mMainView = mainView = createNewWebView(); } - mainView.setWebViewClient(mActivity.getWebViewClient()); - mainView.setWebChromeClient(mActivity.getWebChromeClient()); - mainView.setOnCreateContextMenuListener(mActivity); - mainView.setDownloadListener(mActivity); + putViewInForeground(mainView, mActivity.getWebViewClient(), + mActivity.getWebChromeClient()); // Add the subwindow if it exists if (newTab.mSubViewContainer != null) { - subView = newTab.mSubView; - subView.setWebViewClient(newTab.mSubViewClient); - subView.setWebChromeClient(newTab.mSubViewChromeClient); - subView.setOnCreateContextMenuListener(mActivity); - subView.setDownloadListener(mActivity); + putViewInForeground(newTab.mSubView, newTab.mSubViewClient, + newTab.mSubViewChromeClient); } if (needRestore) { // Have to finish setCurrentTab work before calling restoreState @@ -925,23 +964,9 @@ class TabControl { * Put the tab in the background using all the empty/background clients. */ private void putTabInBackground(Tab t) { - WebView mainView = t.mMainView; - // Set an empty callback so that default actions are not triggered. - mainView.setWebViewClient(mEmptyClient); - mainView.setWebChromeClient(mBackgroundChromeClient); - mainView.setOnCreateContextMenuListener(null); - // Leave the DownloadManager attached so that downloads can start in - // a non-active window. This can happen when going to a site that does - // a redirect after a period of time. The user could have switched to - // another tab while waiting for the download to start. - mainView.setDownloadListener(mActivity); - WebView subView = t.mSubView; - if (subView != null) { - // Set an empty callback so that default actions are not triggered. - subView.setWebViewClient(mEmptyClient); - subView.setWebChromeClient(mBackgroundChromeClient); - subView.setOnCreateContextMenuListener(null); - subView.setDownloadListener(mActivity); + putViewInBackground(t.mMainView); + if (t.mSubView != null) { + putViewInBackground(t.mSubView); } } diff --git a/src/com/android/browser/TitleBar.java b/src/com/android/browser/TitleBar.java new file mode 100644 index 0000000..b818dd2 --- /dev/null +++ b/src/com/android/browser/TitleBar.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +public class TitleBar extends LinearLayout { + private TextView mTitle; + private TextView mUrl; + private ImageView mLftButton; + private Drawable mBookmarkDrawable; + private View mRtButton; + private View mDivider; + private ProgressBar mCircularProgress; + private ProgressBar mHorizontalProgress; + private ImageView mFavicon; + private ImageView mLockIcon; + private boolean mInLoad; + + public TitleBar(Context context) { + this(context, null); + } + + public TitleBar(Context context, AttributeSet attrs) { + super(context, attrs); + LayoutInflater factory = LayoutInflater.from(context); + factory.inflate(R.layout.title_bar, this); + + mTitle = (TextView) findViewById(R.id.title); + mUrl = (TextView) findViewById(R.id.url); + + mLftButton = (ImageView) findViewById(R.id.lft_button); + mRtButton = findViewById(R.id.rt_button); + + mCircularProgress = (ProgressBar) findViewById(R.id.progress_circular); + mHorizontalProgress = (ProgressBar) findViewById( + R.id.progress_horizontal); + mFavicon = (ImageView) findViewById(R.id.favicon); + mLockIcon = (ImageView) findViewById(R.id.lock_icon); + mDivider = findViewById(R.id.divider); + } + + /* package */ void setBrowserActivity(final BrowserActivity activity) { + mLftButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (mInLoad) { + WebView webView = activity.getTopWindow(); + if (webView != null) { + webView.stopLoading(); + } + } else { + activity.bookmarksOrHistoryPicker(false); + } + } + }); + mRtButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + WebView webView = activity.getTopWindow(); + if (webView != null) { + webView.zoomScrollOut(); + } + } + }); + setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + activity.onSearchRequested(); + } + }); + } + + /* package */ void setFavicon(Drawable d) { + mFavicon.setImageDrawable(d); + } + + /* package */ void setLock(Drawable d) { + if (d == null) { + mLockIcon.setVisibility(View.GONE); + } else { + mLockIcon.setImageDrawable(d); + mLockIcon.setVisibility(View.VISIBLE); + } + } + + /* package */ void setProgress(int newProgress) { + if (newProgress == mCircularProgress.getMax()) { + mCircularProgress.setVisibility(View.GONE); + mHorizontalProgress.setVisibility(View.GONE); + mDivider.setVisibility(View.VISIBLE); + mRtButton.setVisibility(View.VISIBLE); + mLftButton.setImageDrawable(mBookmarkDrawable); + mInLoad = false; + } else { + mCircularProgress.setProgress(newProgress); + mHorizontalProgress.setProgress(newProgress); + mCircularProgress.setVisibility(View.VISIBLE); + mHorizontalProgress.setVisibility(View.VISIBLE); + mDivider.setVisibility(View.GONE); + mRtButton.setVisibility(View.GONE); + if (mBookmarkDrawable == null) { + // The drawable was assigned in the xml file, so it already + // exists. Keep a pointer to it when we switch to the resource + // so we can easily switch back. + mBookmarkDrawable = mLftButton.getDrawable(); + } + mLftButton.setImageResource( + com.android.internal.R.drawable.ic_menu_stop); + mInLoad = true; + } + } + + /* package */ void setTitleAndUrl(CharSequence title, CharSequence url) { + if (null == title) { + mTitle.setText(R.string.title_bar_loading); + } else { + mTitle.setText(title); + } + if (url != null) { + url = BrowserActivity.buildTitleUrl(url.toString()); + } + mUrl.setText(url); + } + + /* package */ void setToTabPicker() { + mTitle.setText(R.string.tab_picker_title); + setFavicon(null); + setLock(null); + mCircularProgress.setVisibility(View.GONE); + mHorizontalProgress.setVisibility(View.GONE); + } +} diff --git a/src/com/android/browser/WebsiteSettingsActivity.java b/src/com/android/browser/WebsiteSettingsActivity.java new file mode 100644 index 0000000..7fea766 --- /dev/null +++ b/src/com/android/browser/WebsiteSettingsActivity.java @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Browser; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebIconDatabase; +import android.webkit.WebStorage; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +/** + * Manage the settings for an origin. + * We use it to keep track of the HTML5 settings, i.e. database (webstorage). + */ +public class WebsiteSettingsActivity extends ListActivity { + + private String LOGTAG = "WebsiteSettingsActivity"; + private static String sMBStored = null; + private SiteAdapter mAdapter = null; + + class Site { + private String mOrigin; + private String mTitle; + private Bitmap mIcon; + + public Site(String origin, String title, Bitmap icon) { + mOrigin = origin; + mTitle = title; + mIcon = icon; + } + + public String getOrigin() { + return mOrigin; + } + + public void setTitle(String title) { + mTitle = title; + } + + public String getTitle() { + return mTitle; + } + + public void setIcon(Bitmap icon) { + mIcon = icon; + } + + public Bitmap getIcon() { + return mIcon; + } + } + + class SiteAdapter extends ArrayAdapter<Site> + implements AdapterView.OnItemClickListener { + private int mResource; + private LayoutInflater mInflater; + private Bitmap mDefaultIcon; + private Site mCurrentSite; + private final static int STORED_DATA = 0; + + public SiteAdapter(Context context, int rsc) { + super(context, rsc); + mResource = rsc; + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDefaultIcon = BitmapFactory.decodeResource(getResources(), + R.drawable.ic_launcher_shortcut_browser_bookmark); + populateOrigins(); + } + + public void populateOrigins() { + clear(); + + // Get the list of origins we want to display + HashMap<String, Site> uris = new HashMap<String, Site>(); + Vector origins = WebStorage.getInstance().getOrigins(); + if (origins != null) { + for (int i = 0; i < origins.size(); i++) { + String origin = (String) origins.get(i); + Site site = new Site(origin, origin, null); + uris.put(Uri.parse(origin).getHost(), site); + } + } + + // Check the bookmark db -- if one of our origin matches, + // we set its title and favicon + Cursor c = getContext().getContentResolver().query(Browser.BOOKMARKS_URI, + new String[] { Browser.BookmarkColumns.URL, Browser.BookmarkColumns.TITLE, + Browser.BookmarkColumns.FAVICON }, "bookmark = 1", null, null); + + if ((c != null) && c.moveToFirst()) { + int urlIndex = c.getColumnIndex(Browser.BookmarkColumns.URL); + int titleIndex = c.getColumnIndex(Browser.BookmarkColumns.TITLE); + int faviconIndex = c.getColumnIndex(Browser.BookmarkColumns.FAVICON); + do { + String url = c.getString(urlIndex); + String host = Uri.parse(url).getHost(); + if (uris.containsKey(host)) { + String title = c.getString(titleIndex); + Site site = uris.get(host); + site.setTitle(title); + byte[] data = c.getBlob(faviconIndex); + if (data != null) { + Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length); + if (bmp != null) { + site.setIcon(bmp); + } + } + } + } while (c.moveToNext()); + } + + // We can now simply populate our array with Site instances + Set keys = uris.keySet(); + Iterator iter = keys.iterator(); + while (iter.hasNext()) { + String origin = (String) iter.next(); + Site site = uris.get(origin); + add(site); + } + + if (getCount() == 0) { + finish(); // we close the screen + } + } + + public int getCount() { + if (mCurrentSite == null) { + return super.getCount(); + } + return 1; // db view + } + + public String sizeValueToString(long value) { + float mb = (float) value / (1024.0F * 1024.0F); + int val = (int) (mb * 10); + float ret = (float) (val / 10.0F); + if (ret <= 0) { + return "0"; + } + return String.valueOf(ret); + } + + /* + * If we receive the back event and are displaying + * site's settings, we want to go back to the main + * list view. If not, we just do nothing (see + * dispatchKeyEvent() below). + */ + public boolean backKeyPressed() { + if (mCurrentSite != null) { + mCurrentSite = null; + populateOrigins(); + notifyDataSetChanged(); + return true; + } + return false; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view; + TextView title; + TextView subtitle; + ImageView icon; + + if (convertView == null) { + view = mInflater.inflate(mResource, parent, false); + } else { + view = convertView; + } + + title = (TextView) view.findViewById(R.id.title); + subtitle = (TextView) view.findViewById(R.id.subtitle); + icon = (ImageView) view.findViewById(R.id.icon); + + if (mCurrentSite == null) { + Site site = getItem(position); + title.setText(site.getTitle()); + subtitle.setText(site.getOrigin()); + icon.setVisibility(View.VISIBLE); + Bitmap bmp = site.getIcon(); + if (bmp == null) { + bmp = mDefaultIcon; + } + icon.setImageBitmap(bmp); + // We set the site as the view's tag, + // so that we can get it in onItemClick() + view.setTag(site); + } else { + icon.setVisibility(View.GONE); + if (position == STORED_DATA) { + String origin = mCurrentSite.getOrigin(); + long usageValue = WebStorage.getInstance().getUsageForOrigin(origin); + String usage = sizeValueToString(usageValue) + " " + sMBStored; + + title.setText(R.string.webstorage_clear_data_title); + subtitle.setText(usage); + } + } + + return view; + } + + public void onItemClick(AdapterView<?> parent, + View view, + int position, + long id) { + if (mCurrentSite != null) { + if (position == STORED_DATA) { + new AlertDialog.Builder(getContext()) + .setTitle(R.string.webstorage_clear_data_dialog_title) + .setMessage(R.string.webstorage_clear_data_dialog_message) + .setPositiveButton(R.string.webstorage_clear_data_dialog_ok_button, + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dlg, int which) { + WebStorage.getInstance().deleteOrigin(mCurrentSite.getOrigin()); + mCurrentSite = null; + populateOrigins(); + notifyDataSetChanged(); + }}) + .setNegativeButton(R.string.webstorage_clear_data_dialog_cancel_button, null) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + } else { + mCurrentSite = (Site) view.getTag(); + notifyDataSetChanged(); + } + } + } + + /** + * Intercepts the back key to immediately notify + * NativeDialog that we are done. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + if ((event.getKeyCode() == KeyEvent.KEYCODE_BACK) + && (event.getAction() == KeyEvent.ACTION_DOWN)) { + if ((mAdapter != null) && (mAdapter.backKeyPressed())){ + return true; // event consumed + } + } + return super.dispatchKeyEvent(event); + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + if (sMBStored == null) { + sMBStored = getString(R.string.webstorage_origin_summary_mb_stored); + } + mAdapter = new SiteAdapter(this, R.layout.application); + setListAdapter(mAdapter); + getListView().setOnItemClickListener(mAdapter); + } +} |