diff options
Diffstat (limited to 'src/com/android')
| -rw-r--r-- | src/com/android/browser/ActiveTabsPage.java | 14 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserActivity.java | 298 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserDownloadAdapter.java | 6 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserDownloadPage.java | 11 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserHistoryPage.java | 109 | ||||
| -rw-r--r-- | src/com/android/browser/DateSortedExpandableListAdapter.java | 8 | ||||
| -rw-r--r-- | src/com/android/browser/FindDialog.java | 132 | ||||
| -rw-r--r-- | src/com/android/browser/Tab.java | 67 |
8 files changed, 473 insertions, 172 deletions
diff --git a/src/com/android/browser/ActiveTabsPage.java b/src/com/android/browser/ActiveTabsPage.java index 2de778712..52828b301 100644 --- a/src/com/android/browser/ActiveTabsPage.java +++ b/src/com/android/browser/ActiveTabsPage.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.util.AttributeSet; +import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +33,7 @@ import android.widget.ListView; import android.widget.TextView; public class ActiveTabsPage extends LinearLayout { + private static final String LOGTAG = "TabPicker"; private final BrowserActivity mBrowserActivity; private final LayoutInflater mFactory; private final TabControl mControl; @@ -152,7 +154,19 @@ public class ActiveTabsPage extends LinearLayout { (ImageView) convertView.findViewById(R.id.favicon); View close = convertView.findViewById(R.id.close); Tab tab = mControl.getTab(position); + if (tab.getWebView() == null) { + // This means that populatePickerData will have to use the + // saved state. + Log.w(LOGTAG, "Tab " + position + " has a null WebView and " + + (tab.getSavedState() == null ? "null" : "non-null") + + " saved state "); + } tab.populatePickerData(); + if (tab.getTitle() == null || tab.getTitle().length() == 0) { + Log.w(LOGTAG, "Tab " + position + " has no title. " + + "Check above in the Logs to see whether it has a " + + "null WebView or null WebHistoryItem"); + } title.setText(tab.getTitle()); url.setText(tab.getUrl()); Bitmap icon = tab.getFavicon(); diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java index 5e557893b..b2d7d82c1 100644 --- a/src/com/android/browser/BrowserActivity.java +++ b/src/com/android/browser/BrowserActivity.java @@ -87,6 +87,7 @@ import android.view.Window; import android.view.WindowManager; import android.view.ContextMenu.ContextMenuInfo; import android.view.MenuItem.OnMenuItemClickListener; +import android.view.accessibility.AccessibilityManager; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.DownloadListener; @@ -171,6 +172,8 @@ public class BrowserActivity extends Activity */ private FrameLayout mBrowserFrameLayout; + private boolean mXLargeScreenSize; + @Override public void onCreate(Bundle icicle) { if (LOGV_ENABLED) { @@ -186,7 +189,11 @@ public class BrowserActivity extends Activity BitmapFactory.setDefaultConfig(Bitmap.Config.ARGB_8888); } - setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + if (AccessibilityManager.getInstance(this).isEnabled()) { + setDefaultKeyMode(DEFAULT_KEYS_DISABLE); + } else { + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); + } mResolver = getContentResolver(); @@ -214,9 +221,22 @@ public class BrowserActivity extends Activity .findViewById(R.id.fullscreen_custom_content); frameLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS); mTitleBar = new TitleBar(this); - // mTitleBar will be always shown in the fully loaded mode - mTitleBar.setProgress(100); - mFakeTitleBar = new TitleBar(this); + mXLargeScreenSize = (getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + == Configuration.SCREENLAYOUT_SIZE_XLARGE; + if (mXLargeScreenSize) { + LinearLayout layout = (LinearLayout) mBrowserFrameLayout. + findViewById(R.id.vertical_layout); + layout.addView(mTitleBar, 0, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + } else { + // mTitleBar will be always be shown in the fully loaded mode on + // phone + mTitleBar.setProgress(100); + // Fake title bar is not needed in xlarge layout + mFakeTitleBar = new TitleBar(this); + } // Create the tab control and our initial tab mTabControl = new TabControl(this); @@ -305,9 +325,7 @@ public class BrowserActivity extends Activity } if (permissionOk) { PluginManager.getInstance(BrowserActivity.this) - .refreshPlugins( - Intent.ACTION_PACKAGE_ADDED - .equals(action)); + .refreshPlugins(true); } } } @@ -697,17 +715,21 @@ public class BrowserActivity extends Activity } /* package */ void showVoiceTitleBar(String title) { mTitleBar.setInVoiceMode(true); - mFakeTitleBar.setInVoiceMode(true); - mTitleBar.setDisplayTitle(title); - mFakeTitleBar.setDisplayTitle(title); + + if (!mXLargeScreenSize) { + mFakeTitleBar.setInVoiceMode(true); + mFakeTitleBar.setDisplayTitle(title); + } } /* package */ void revertVoiceTitleBar() { mTitleBar.setInVoiceMode(false); - mFakeTitleBar.setInVoiceMode(false); - mTitleBar.setDisplayTitle(mUrl); - mFakeTitleBar.setDisplayTitle(mUrl); + + if (!mXLargeScreenSize) { + mFakeTitleBar.setInVoiceMode(false); + mFakeTitleBar.setDisplayTitle(mUrl); + } } /* package */ static String fixUrl(String inUrl) { // FIXME: Converting the url to lower case @@ -827,6 +849,7 @@ public class BrowserActivity extends Activity } private void showFakeTitleBar() { + if (mXLargeScreenSize) return; if (mFakeTitleBar.getParent() == null && mActiveTabsPage == null && !mActivityInPause) { WebView mainView = mTabControl.getCurrentWebView(); @@ -834,6 +857,13 @@ public class BrowserActivity extends Activity if (mainView == null) { return; } + // Do not need to check for null, since the current tab will have + // at least a main WebView, or we would have returned above. + if (getTopWindow().getFindIsUp()) { + // Do not show the fake title bar, which would cover up the + // FindDialog. + return; + } WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); @@ -868,7 +898,7 @@ public class BrowserActivity extends Activity } private void hideFakeTitleBar() { - if (mFakeTitleBar.getParent() == null) return; + if (mXLargeScreenSize || mFakeTitleBar.getParent() == null) return; WindowManager.LayoutParams params = (WindowManager.LayoutParams) mFakeTitleBar.getLayoutParams(); WebView mainView = mTabControl.getCurrentWebView(); @@ -1145,7 +1175,6 @@ public class BrowserActivity extends Activity break; // -- Browser context menu case R.id.open_context_menu_id: - case R.id.open_newtab_context_menu_id: case R.id.bookmark_context_menu_id: case R.id.save_link_context_menu_id: case R.id.share_link_context_menu_id: @@ -1263,6 +1292,7 @@ public class BrowserActivity extends Activity */ /* package */ void removeActiveTabPage(boolean needToAttach) { mContentView.removeView(mActiveTabsPage); + mTitleBar.setVisibility(View.VISIBLE); mActiveTabsPage = null; mMenuState = R.id.MAIN_MENU; if (needToAttach) { @@ -1305,6 +1335,7 @@ public class BrowserActivity extends Activity case R.id.active_tabs_menu_id: mActiveTabsPage = new ActiveTabsPage(this, mTabControl); removeTabFromContentView(mTabControl.getCurrentTab()); + mTitleBar.setVisibility(View.GONE); hideFakeTitleBar(); mContentView.addView(mActiveTabsPage, COVER_SCREEN_PARAMS); mActiveTabsPage.requestFocus(); @@ -1367,9 +1398,18 @@ public class BrowserActivity extends Activity if (null == mFindDialog) { mFindDialog = new FindDialog(this); } - mFindDialog.setWebView(getTopWindow()); - mFindDialog.show(); - getTopWindow().setFindIsUp(true); + // Need to do something special for Tablet + Tab tab = mTabControl.getCurrentTab(); + if (tab.getSubWebView() == null) { + // If the Find is being performed on the main webview, + // remove the embedded title bar. + WebView mainView = tab.getWebView(); + if (mainView != null) { + mainView.setEmbeddedTitleBar(null); + } + } + hideFakeTitleBar(); + tab.showFind(mFindDialog); mMenuState = EMPTY_MENU; break; @@ -1450,8 +1490,32 @@ public class BrowserActivity extends Activity return true; } + /* + * Remove the FindDialog. + */ public void closeFind() { + Tab currentTab = mTabControl.getCurrentTab(); + if (mFindDialog != null) { + currentTab.closeFind(mFindDialog); + mFindDialog.dismiss(); + } + if (!mXLargeScreenSize) { + // If the Find was being performed in the main WebView, replace the + // embedded title bar. + if (currentTab.getSubWebView() == null) { + WebView mainView = currentTab.getWebView(); + if (mainView != null) { + mainView.setEmbeddedTitleBar(mTitleBar); + } + } + } mMenuState = R.id.MAIN_MENU; + if (mInLoad) { + // The title bar was hidden, because otherwise it would cover up the + // find dialog. Now that the dialog has been removed, show the fake + // title bar once again. + showFakeTitleBar(); + } } @Override @@ -1551,7 +1615,7 @@ public class BrowserActivity extends Activity inflater.inflate(R.menu.browsercontext, menu); // Show the correct menu group - String extra = result.getExtra(); + final String extra = result.getExtra(); menu.setGroupVisible(R.id.PHONE_MENU, type == WebView.HitTestResult.PHONE_TYPE); menu.setGroupVisible(R.id.EMAIL_MENU, @@ -1608,8 +1672,23 @@ public class BrowserActivity extends Activity titleView.setText(extra); menu.setHeaderView(titleView); // decide whether to show the open link in new tab option - menu.findItem(R.id.open_newtab_context_menu_id).setVisible( - mTabControl.canCreateNewTab()); + boolean showNewTab = mTabControl.canCreateNewTab(); + MenuItem newTabItem + = menu.findItem(R.id.open_newtab_context_menu_id); + newTabItem.setVisible(showNewTab); + if (showNewTab) { + newTabItem.setOnMenuItemClickListener( + new MenuItem.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + final Tab parent = mTabControl.getCurrentTab(); + final Tab newTab = openTab(extra); + if (newTab != parent) { + parent.addChildTab(newTab); + } + return true; + } + }); + } menu.findItem(R.id.bookmark_context_menu_id).setVisible( Bookmarks.urlHasAcceptableScheme(extra)); PackageManager pm = getPackageManager(); @@ -1660,8 +1739,10 @@ public class BrowserActivity extends Activity ViewGroup.LayoutParams.WRAP_CONTENT)); } - WebView view = t.getWebView(); - view.setEmbeddedTitleBar(mTitleBar); + if (!mXLargeScreenSize){ + WebView view = t.getWebView(); + view.setEmbeddedTitleBar(mTitleBar); + } if (t.isInVoiceSearchMode()) { showVoiceTitleBar(t.getVoiceDisplayTitle()); } else { @@ -1687,9 +1768,11 @@ public class BrowserActivity extends Activity mErrorConsoleContainer.removeView(errorConsole); } - WebView view = t.getWebView(); - if (view != null) { - view.setEmbeddedTitleBar(null); + if (!mXLargeScreenSize) { + WebView view = t.getWebView(); + if (view != null) { + view.setEmbeddedTitleBar(null); + } } } @@ -1919,7 +2002,9 @@ public class BrowserActivity extends Activity // If we are in voice search mode, the title has already been set. if (mTabControl.getCurrentTab().isInVoiceSearchMode()) return; mTitleBar.setDisplayTitle(url); - mFakeTitleBar.setDisplayTitle(url); + if (!mXLargeScreenSize) { + mFakeTitleBar.setDisplayTitle(url); + } } /** @@ -1962,7 +2047,9 @@ public class BrowserActivity extends Activity // Set the favicon in the title bar. void setFavicon(Bitmap icon) { mTitleBar.setFavicon(icon); - mFakeTitleBar.setFavicon(icon); + if (!mXLargeScreenSize) { + mFakeTitleBar.setFavicon(icon); + } } /** @@ -2192,13 +2279,6 @@ public class BrowserActivity extends Activity case R.id.view_image_context_menu_id: loadUrlFromContext(getTopWindow(), url); break; - case R.id.open_newtab_context_menu_id: - final Tab parent = mTabControl.getCurrentTab(); - final Tab newTab = openTab(url); - if (newTab != parent) { - parent.addChildTab(newTab); - } - break; case R.id.bookmark_context_menu_id: Intent intent = new Intent(BrowserActivity.this, AddBookmarkPage.class); @@ -2207,41 +2287,8 @@ public class BrowserActivity extends Activity startActivity(intent); break; case R.id.share_link_context_menu_id: - // See if this site has been visited before - StringBuilder sb = new StringBuilder( - Browser.BookmarkColumns.URL + " = "); - DatabaseUtils.appendEscapedSQLString(sb, url); - Cursor c = mResolver.query(Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, - sb.toString(), - null, + sharePage(BrowserActivity.this, title, url, null, null); - if (c.moveToFirst()) { - // The site has been visited before, so grab the - // info from the database. - Bitmap favicon = null; - Bitmap thumbnail = null; - String linkTitle = c.getString(Browser. - HISTORY_PROJECTION_TITLE_INDEX); - byte[] data = c.getBlob(Browser. - HISTORY_PROJECTION_FAVICON_INDEX); - if (data != null) { - favicon = BitmapFactory.decodeByteArray( - data, 0, data.length); - } - data = c.getBlob(Browser. - HISTORY_PROJECTION_THUMBNAIL_INDEX); - if (data != null) { - thumbnail = BitmapFactory.decodeByteArray( - data, 0, data.length); - } - sharePage(BrowserActivity.this, - linkTitle, url, favicon, thumbnail); - } else { - Browser.sendString(BrowserActivity.this, url, - getString( - R.string.choosertitle_sharevia)); - } break; case R.id.copy_link_context_menu_id: copy(url); @@ -2462,7 +2509,9 @@ public class BrowserActivity extends Activity onProgressChanged(view, INITIAL_PROGRESS); mDidStopLoad = false; if (!mIsNetworkUp) createAndShowNetworkDialog(); - + if (view.getFindIsUp()) { + closeFind(); + } if (mSettings.isTracing()) { String host; try { @@ -2642,7 +2691,14 @@ public class BrowserActivity extends Activity // ------------------------------------------------------------------------- void onProgressChanged(WebView view, int newProgress) { - mFakeTitleBar.setProgress(newProgress); + if (mXLargeScreenSize) { + mTitleBar.setProgress(newProgress); + } else { + // On the phone, the fake title bar will always cover up the + // regular title bar (or the regular one is offscreen), so only the + // fake title bar needs to change its progress + mFakeTitleBar.setProgress(newProgress); + } if (newProgress == 100) { // onProgressChanged() may continue to be called after the main @@ -2743,15 +2799,85 @@ public class BrowserActivity extends Activity * The Object used to inform the WebView of the file to upload. */ private ValueCallback<Uri> mUploadMessage; + private String mCameraFilePath; + + void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { + + final String imageMimeType = "image/*"; + final String imageSourceKey = "source"; + final String imageSourceValueCamera = "camera"; + final String imageSourceValueGallery = "gallery"; + + // image source can be 'gallery' or 'camera'. + String imageSource = ""; + + // We add the camera intent if there was no accept type (or '*/*') or 'image/*'. + boolean addCameraIntent = true; - void openFileChooser(ValueCallback<Uri> uploadMsg) { if (mUploadMessage != null) return; mUploadMessage = uploadMsg; + + // Parse the accept type. + String params[] = acceptType.split(";"); + String mimeType = params[0]; + + for (String p : params) { + String[] keyValue = p.split("="); + if (keyValue.length == 2) { + // Process key=value parameters. + if (imageSourceKey.equals(keyValue[0])) { + imageSource = keyValue[1]; + } + } + } + + // This intent will display the standard OPENABLE file picker. Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); - BrowserActivity.this.startActivityForResult(Intent.createChooser(i, - getString(R.string.choose_upload)), FILE_SELECTED); + + // Create an intent to add to the standard file picker that will + // capture an image from the camera. We'll combine this intent with + // the standard OPENABLE picker unless the web developer specifically + // requested the camera or gallery be opened by passing a parameter + // in the accept type. + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File externalDataDir = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DCIM); + File cameraDataDir = new File(externalDataDir.getAbsolutePath() + + File.separator + "browser-photos"); + cameraDataDir.mkdirs(); + mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + + System.currentTimeMillis() + ".jpg"; + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); + + if (mimeType.equals(imageMimeType)) { + i.setType(imageMimeType); + if (imageSource.equals(imageSourceValueCamera)) { + // Specified 'image/*' and requested the camera, so go ahead and launch the camera + // directly. + BrowserActivity.this.startActivityForResult(cameraIntent, FILE_SELECTED); + return; + } else if (imageSource.equals(imageSourceValueGallery)) { + // Specified gallery as the source, so don't want to consider the camera. + addCameraIntent = false; + } + } else { + i.setType("*/*"); + } + + // Combine the chooser and the extra choices (like camera) + Intent chooser = new Intent(Intent.ACTION_CHOOSER); + chooser.putExtra(Intent.EXTRA_INTENT, i); + + if (addCameraIntent) { + // Add the camera Intent + Intent[] choices = new Intent[1]; + choices[0] = cameraIntent; + chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, choices); + } + + chooser.putExtra(Intent.EXTRA_TITLE, getString(R.string.choose_upload)); + BrowserActivity.this.startActivityForResult(chooser, FILE_SELECTED); } // ------------------------------------------------------------------------- @@ -2952,7 +3078,9 @@ public class BrowserActivity extends Activity d = mMixLockIcon; } mTitleBar.setLock(d); - mFakeTitleBar.setLock(d); + if (!mXLargeScreenSize) { + mFakeTitleBar.setLock(d); + } } /** @@ -3468,8 +3596,26 @@ public class BrowserActivity extends Activity if (null == mUploadMessage) break; Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); + + // As we ask the camera to save the result of the user taking + // a picture, the camera application does not return anything other + // than RESULT_OK. So we need to check whether the file we expected + // was written to disk in the in the case that we + // did not get an intent returned but did get a RESULT_OK. If it was, + // we assume that this result has came back from the camera. + if (result == null && intent == null && resultCode == RESULT_OK) { + File cameraFile = new File(mCameraFilePath); + if (cameraFile.exists()) { + result = Uri.fromFile(cameraFile); + // Broadcast to the media scanner that we have a new photo + // so it will be added into the gallery for the user. + sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); + } + } + mUploadMessage.onReceiveValue(result); mUploadMessage = null; + mCameraFilePath = null; break; default: break; diff --git a/src/com/android/browser/BrowserDownloadAdapter.java b/src/com/android/browser/BrowserDownloadAdapter.java index 0f8f721e2..f22c9fe60 100644 --- a/src/com/android/browser/BrowserDownloadAdapter.java +++ b/src/com/android/browser/BrowserDownloadAdapter.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.drm.mobile1.DrmRawContent; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Handler; import android.provider.Downloads; import android.text.format.Formatter; import android.view.LayoutInflater; @@ -55,8 +56,9 @@ public class BrowserDownloadAdapter extends DateSortedExpandableListAdapter { private int mMimetypeColumnId; private int mDateColumnId; - public BrowserDownloadAdapter(Context context, Cursor c, int index) { - super(context, c, index); + public BrowserDownloadAdapter(Context context, Cursor c, int index, + Handler handler) { + super(context, c, index, handler); mTitleColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TITLE); mDescColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_DESCRIPTION); mStatusColumnId = c.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); diff --git a/src/com/android/browser/BrowserDownloadPage.java b/src/com/android/browser/BrowserDownloadPage.java index 18faf8b36..bbf1191b9 100644 --- a/src/com/android/browser/BrowserDownloadPage.java +++ b/src/com/android/browser/BrowserDownloadPage.java @@ -63,6 +63,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { // Only meaningful while a ContentObserver is registered. The ContextMenu // will be reopened on this View. private View mSelectedView; + private Handler mHandler; private final static String LOGTAG = "BrowserDownloadPage"; @Override @@ -85,7 +86,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { Downloads.Impl._DATA, Downloads.Impl.COLUMN_MIME_TYPE}, null, Downloads.Impl.COLUMN_LAST_MODIFICATION + " DESC"); - + mHandler = new Handler(); // only attach everything to the listbox if we can access // the download database. Otherwise, just show it empty if (mDownloadCursor != null) { @@ -99,7 +100,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { // Create a list "controller" for the data mDownloadAdapter = new BrowserDownloadAdapter(this, mDownloadCursor, mDownloadCursor.getColumnIndexOrThrow( - Downloads.Impl.COLUMN_LAST_MODIFICATION)); + Downloads.Impl.COLUMN_LAST_MODIFICATION), mHandler); setListAdapter(mDownloadAdapter); mListView.setOnCreateContextMenuListener(this); @@ -241,8 +242,8 @@ public class BrowserDownloadPage extends ExpandableListActivity { */ private class ChangeObserver extends ContentObserver { private final Uri mTrack; - public ChangeObserver(Uri track) { - super(new Handler()); + public ChangeObserver(Uri track, Handler handler) { + super(handler); mTrack = track; } @@ -313,7 +314,7 @@ public class BrowserDownloadPage extends ExpandableListActivity { getContentResolver().unregisterContentObserver( mContentObserver); } - mContentObserver = new ChangeObserver(track); + mContentObserver = new ChangeObserver(track, mHandler); mSelectedView = v; getContentResolver().registerContentObserver(track, false, mContentObserver); diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java index 23080f86b..028108738 100644 --- a/src/com/android/browser/BrowserHistoryPage.java +++ b/src/com/android/browser/BrowserHistoryPage.java @@ -25,7 +25,10 @@ import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Message; import android.os.ServiceManager; import android.provider.Browser; import android.text.IClipboard; @@ -92,47 +95,75 @@ public class BrowserHistoryPage extends ExpandableListActivity { } } + private static final int ADAPTER_CREATED = 1000; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case ADAPTER_CREATED: + mAdapter = (HistoryAdapter) msg.obj; + setListAdapter(mAdapter); + final ExpandableListView list = getExpandableListView(); + // Add an empty view late, so it does not claim an empty + // history before the adapter is present + View v = new ViewStub(BrowserHistoryPage.this, + R.layout.empty_history); + addContentView(v, new LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + list.setEmptyView(v); + list.setOnCreateContextMenuListener( + BrowserHistoryPage.this); + // Do not post the runnable if there is nothing in the list. + if (list.getExpandableListAdapter().getGroupCount() > 0) { + list.post(new Runnable() { + public void run() { + // In case the history gets cleared before this + // event happens + if (list.getExpandableListAdapter() + .getGroupCount() > 0) { + list.expandGroup(0); + } + } + }); + } + break; + } + } + }; + @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); setTitle(R.string.browser_history); - 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"; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... unused) { + 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"; - Cursor cursor = managedQuery( - Browser.BOOKMARKS_URI, - Browser.HISTORY_PROJECTION, - whereClause, null, orderBy); + Cursor cursor = managedQuery( + Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, + whereClause, null, orderBy); - mAdapter = new HistoryAdapter(this, cursor, - Browser.HISTORY_PROJECTION_DATE_INDEX); - setListAdapter(mAdapter); - final ExpandableListView list = getExpandableListView(); - list.setOnCreateContextMenuListener(this); - View v = new ViewStub(this, R.layout.empty_history); - addContentView(v, new LayoutParams(LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT)); - list.setEmptyView(v); - // Do not post the runnable if there is nothing in the list. - if (list.getExpandableListAdapter().getGroupCount() > 0) { - list.post(new Runnable() { - public void run() { - // In case the history gets cleared before this event - // happens. - if (list.getExpandableListAdapter().getGroupCount() > 0) { - list.expandGroup(0); - } - } - }); - } + HistoryAdapter adapter = new HistoryAdapter( + BrowserHistoryPage.this, cursor, + Browser.HISTORY_PROJECTION_DATE_INDEX, mHandler); + mHandler.obtainMessage(ADAPTER_CREATED, adapter).sendToTarget(); + return null; + } + }.execute(); mDisableNewWindow = getIntent().getBooleanExtra("disable_new_window", false); @@ -153,6 +184,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { @Override protected void onDestroy() { + mHandler.removeCallbacksAndMessages(null); super.onDestroy(); CombinedBookmarkHistoryActivity.getIconListenerSet() .removeListener(mIconReceiver); @@ -181,7 +213,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { // CombinedBookmarkHistoryActivity ((CombinedBookmarkHistoryActivity) getParent()) .removeParentChildRelationShips(); - mAdapter.refreshData(); + if (mAdapter != null) mAdapter.refreshData(); return true; default: @@ -265,7 +297,7 @@ public class BrowserHistoryPage extends ExpandableListActivity { return true; case R.id.delete_context_menu_id: Browser.deleteFromHistory(getContentResolver(), url); - mAdapter.refreshData(); + if (mAdapter != null) mAdapter.refreshData(); return true; case R.id.homepage_context_menu_id: BrowserSettings.getInstance().setHomePage(this, url); @@ -297,8 +329,9 @@ public class BrowserHistoryPage extends ExpandableListActivity { } private class HistoryAdapter extends DateSortedExpandableListAdapter { - HistoryAdapter(Context context, Cursor cursor, int index) { - super(context, cursor, index); + HistoryAdapter(Context context, Cursor cursor, int index, + Handler handler) { + super(context, cursor, index, handler); } diff --git a/src/com/android/browser/DateSortedExpandableListAdapter.java b/src/com/android/browser/DateSortedExpandableListAdapter.java index 1d04493aa..f8261d835 100644 --- a/src/com/android/browser/DateSortedExpandableListAdapter.java +++ b/src/com/android/browser/DateSortedExpandableListAdapter.java @@ -51,8 +51,8 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter { private Context mContext; private class ChangeObserver extends ContentObserver { - public ChangeObserver() { - super(new Handler()); + public ChangeObserver(Handler handler) { + super(handler); } @Override @@ -67,13 +67,13 @@ public class DateSortedExpandableListAdapter implements ExpandableListAdapter { } public DateSortedExpandableListAdapter(Context context, Cursor cursor, - int dateIndex) { + int dateIndex, Handler handler) { mContext = context; mDateSorter = new DateSorter(context); mObservers = new Vector<DataSetObserver>(); mCursor = cursor; mIdIndex = cursor.getColumnIndexOrThrow(BaseColumns._ID); - cursor.registerContentObserver(new ChangeObserver()); + cursor.registerContentObserver(new ChangeObserver(handler)); mDateIndex = dateIndex; buildMap(); } diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java index 45c801694..bcd5bb7c4 100644 --- a/src/com/android/browser/FindDialog.java +++ b/src/com/android/browser/FindDialog.java @@ -16,24 +16,24 @@ package com.android.browser; -import android.app.Dialog; import android.content.Context; -import android.os.Bundle; import android.text.Editable; +import android.text.Selection; import android.text.Spannable; import android.text.TextWatcher; import android.view.Gravity; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; +import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; import android.widget.EditText; +import android.widget.LinearLayout; import android.widget.TextView; -/* package */ class FindDialog extends Dialog implements TextWatcher { +/* package */ class FindDialog extends LinearLayout implements TextWatcher { private WebView mWebView; private TextView mMatches; private BrowserActivity mBrowserActivity; @@ -44,6 +44,11 @@ import android.widget.TextView; private View mPrevButton; private View mMatchesView; + // When the dialog is opened up with old text, enter needs to be pressed + // (or the text needs to be changed) before WebView.findAll can be called. + // Once it has been called, enter should move to the next match. + private boolean mMatchesFound; + private int mNumberOfMatches; private View.OnClickListener mFindListener = new View.OnClickListener() { public void onClick(View v) { findNext(); @@ -53,7 +58,7 @@ import android.widget.TextView; private View.OnClickListener mFindCancelListener = new View.OnClickListener() { public void onClick(View v) { - dismiss(); + mBrowserActivity.closeFind(); } }; @@ -64,6 +69,7 @@ import android.widget.TextView; throw new AssertionError("No WebView for FindDialog::onClick"); } mWebView.findNext(false); + updateMatchesString(); hideSoftInput(); } }; @@ -89,22 +95,11 @@ import android.widget.TextView; } /* package */ FindDialog(BrowserActivity context) { - super(context, R.style.FindDialogTheme); + super(context); mBrowserActivity = context; - setCanceledOnTouchOutside(true); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Window theWindow = getWindow(); - theWindow.setGravity(Gravity.BOTTOM|Gravity.FILL_HORIZONTAL); - setContentView(R.layout.browser_find); - - theWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + LayoutInflater factory = LayoutInflater.from(context); + factory.inflate(R.layout.browser_find, this); mEditText = (EditText) findViewById(R.id.edit); @@ -122,23 +117,57 @@ import android.widget.TextView; mMatches = (TextView) findViewById(R.id.matches); mMatchesView = findViewById(R.id.matches_view); disableButtons(); - theWindow.setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } - + + /** + * Called by BrowserActivity.closeFind. Start the animation to hide + * the dialog, inform the WebView that the dialog is being dismissed, + * and hide the soft keyboard. + */ public void dismiss() { - super.dismiss(); - mBrowserActivity.closeFind(); mWebView.notifyFindDialogDismissed(); + startAnimation(AnimationUtils.loadAnimation(mBrowserActivity, + R.anim.find_dialog_exit)); + hideSoftInput(); + } + + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + int action = event.getAction(); + if (KeyEvent.ACTION_DOWN == action + && event.getRepeatCount() == 0) { + state.startTracking(event, this); + return true; + } else if (KeyEvent.ACTION_UP == action + && !event.isCanceled() && state.isTracking(event)) { + mBrowserActivity.closeFind(); + return true; + } + } + } + return super.dispatchKeyEventPreIme(event); } @Override public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER - && event.getAction() == KeyEvent.ACTION_UP - && mEditText.hasFocus()) { - findNext(); - return true; + int keyCode = event.getKeyCode(); + if (event.getAction() == KeyEvent.ACTION_UP) { + if (keyCode == KeyEvent.KEYCODE_ENTER + && mEditText.hasFocus()) { + if (mMatchesFound) { + findNext(); + } else { + findAll(); + // Set the selection to the end. + Spannable span = (Spannable) mEditText.getText(); + Selection.setSelection(span, span.length()); + } + return true; + } } return super.dispatchKeyEvent(event); } @@ -148,18 +177,27 @@ import android.widget.TextView; throw new AssertionError("No WebView for FindDialog::findNext"); } mWebView.findNext(true); + updateMatchesString(); hideSoftInput(); } public void show() { - super.show(); + // In case the matches view is showing from a previous search + mMatchesView.setVisibility(View.INVISIBLE); + mMatchesFound = false; + // This text is only here to ensure that mMatches has a height. + mMatches.setText("0"); mEditText.requestFocus(); - mEditText.setText(""); Spannable span = (Spannable) mEditText.getText(); - span.setSpan(this, 0, span.length(), - Spannable.SPAN_INCLUSIVE_INCLUSIVE); - setMatchesFound(0); + int length = span.length(); + Selection.setSelection(span, 0, length); + span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE); disableButtons(); + startAnimation(AnimationUtils.loadAnimation(mBrowserActivity, + R.anim.find_dialog_enter)); + InputMethodManager imm = (InputMethodManager) + mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(mEditText, 0); } // TextWatcher methods @@ -173,9 +211,13 @@ import android.widget.TextView; int start, int before, int count) { + findAll(); + } + + private void findAll() { if (mWebView == null) { throw new AssertionError( - "No WebView for FindDialog::onTextChanged"); + "No WebView for FindDialog::findAll"); } CharSequence find = mEditText.getText(); if (0 == find.length()) { @@ -184,14 +226,16 @@ import android.widget.TextView; mMatchesView.setVisibility(View.INVISIBLE); } else { mMatchesView.setVisibility(View.VISIBLE); - mWebView.setFindDialogHeight( - getWindow().getDecorView().getHeight()); int found = mWebView.findAll(find.toString()); + mMatchesFound = true; setMatchesFound(found); if (found < 2) { disableButtons(); if (found == 0) { - setMatchesFound(0); + // Cannot use getQuantityString, which ignores the "zero" + // quantity. + mMatches.setText(mBrowserActivity.getResources().getString( + R.string.no_matches)); } } else { mPrevButton.setFocusable(true); @@ -203,8 +247,16 @@ import android.widget.TextView; } private void setMatchesFound(int found) { + mNumberOfMatches = found; + updateMatchesString(); + } + + private void updateMatchesString() { + // Note: updateMatchesString is only called by methods that have already + // checked mWebView for null. String template = mBrowserActivity.getResources(). - getQuantityString(R.plurals.matches_found, found, found); + getQuantityString(R.plurals.matches_found, mNumberOfMatches, + mWebView.findIndex() + 1, mNumberOfMatches); mMatches.setText(template); } diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 5350a1840..79c45742c 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -87,7 +87,7 @@ class Tab { // The Geolocation permissions prompt private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; // Main WebView wrapper - private View mContainer; + private LinearLayout mContainer; // Main WebView private WebView mMainView; // Subwindow container @@ -1173,9 +1173,9 @@ class Tab { } @Override - public void openFileChooser(ValueCallback<Uri> uploadMsg) { + public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { if (mInForeground) { - mActivity.openFileChooser(uploadMsg); + mActivity.openFileChooser(uploadMsg, acceptType); } else { uploadMsg.onReceiveValue(null); } @@ -1208,9 +1208,18 @@ class Tab { private static class SubWindowClient extends WebViewClient { // The main WebViewClient. private final WebViewClient mClient; + private final BrowserActivity mBrowserActivity; - SubWindowClient(WebViewClient client) { + SubWindowClient(WebViewClient client, BrowserActivity activity) { mClient = client; + mBrowserActivity = activity; + } + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // Unlike the others, do not call mClient's version, which would + // change the progress bar. However, we do want to remove the + // find dialog. + if (view.getFindIsUp()) mBrowserActivity.closeFind(); } @Override public void doUpdateVisitedHistory(WebView view, String url, @@ -1300,7 +1309,7 @@ class Tab { // The tab consists of a container view, which contains the main // WebView, as well as any other UI elements associated with the tab. - mContainer = mInflateService.inflate(R.layout.tab, null); + mContainer = (LinearLayout) mInflateService.inflate(R.layout.tab, null); mDownloadListener = new DownloadListener() { public void onDownloadStart(String url, String userAgent, @@ -1411,6 +1420,7 @@ class Tab { */ boolean createSubWindow() { if (mSubView == null) { + if (mMainView.getFindIsUp()) mActivity.closeFind(); mSubViewContainer = mInflateService.inflate( R.layout.browser_subwindow, null); mSubView = (WebView) mSubViewContainer.findViewById(R.id.webview); @@ -1419,7 +1429,8 @@ class Tab { mSubView.setMapTrackballToArrowKeys(false); // Enable the built-in zoom mSubView.getSettings().setBuiltInZoomControls(true); - mSubView.setWebViewClient(new SubWindowClient(mWebViewClient)); + mSubView.setWebViewClient(new SubWindowClient(mWebViewClient, + mActivity)); mSubView.setWebChromeClient(new SubWindowChromeClient( mWebChromeClient)); // Set a different DownloadListener for the mSubView, since it will @@ -1457,6 +1468,9 @@ class Tab { */ void dismissSubWindow() { if (mSubView != null) { + if (mSubView.getFindIsUp()) { + mActivity.closeFind(); + } BrowserSettings.getInstance().deleteObserver( mSubView.getSettings()); mSubView.destroy(); @@ -1481,6 +1495,7 @@ class Tab { void removeSubWindow(ViewGroup content) { if (mSubView != null) { content.removeView(mSubViewContainer); + if (mSubView.getFindIsUp()) mActivity.closeFind(); } } @@ -1539,6 +1554,7 @@ class Tab { (FrameLayout) mContainer.findViewById(R.id.webview_wrapper); wrapper.removeView(mMainView); content.removeView(mContainer); + if (mMainView.getFindIsUp()) mActivity.closeFind(); removeSubWindow(content); } @@ -1812,6 +1828,9 @@ class Tab { // FIXME: The only place we cared about subwindow was for // bookmarking (i.e. not when saving state). Was this deliberate? final WebBackForwardList list = mMainView.copyBackForwardList(); + if (list == null) { + Log.w(LOGTAG, "populatePickerData called and WebBackForwardList is null"); + } final WebHistoryItem item = list != null ? list.getCurrentItem() : null; populatePickerData(item); } @@ -1820,7 +1839,9 @@ class Tab { // WebView. private void populatePickerData(WebHistoryItem item) { mPickerData = new PickerData(); - if (item != null) { + if (item == null) { + Log.w(LOGTAG, "populatePickerData called with a null WebHistoryItem"); + } else { mPickerData.mUrl = item.getUrl(); mPickerData.mTitle = item.getTitle(); mPickerData.mFavicon = item.getFavicon(); @@ -1934,4 +1955,36 @@ class Tab { } return true; } + + /* + * Open the find dialog. Called by BrowserActivity. + */ + void showFind(FindDialog dialog) { + LinearLayout container; + WebView view; + if (mSubView != null) { + view = mSubView; + container = (LinearLayout) mSubViewContainer.findViewById( + R.id.inner_container); + } else { + view = mMainView; + container = mContainer; + } + dialog.show(); + container.addView(dialog, 0, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + dialog.setWebView(view); + view.setFindIsUp(true); + } + + /* + * Close the find dialog. Called by BrowserActivity.closeFind. + */ + void closeFind(FindDialog dialog) { + // The dialog may be attached to the subwindow. Ensure that the + // correct parent has it removed. + LinearLayout parent = (LinearLayout) dialog.getParent(); + if (parent != null) parent.removeView(dialog); + } } |
