Move Find on page and Select ActionModes to framework.

Bug: 2938052

Requires a change to packages/apps/Browser

Change-Id: I1bb1a47610b06c4bb02ef08647c02cc1dcaddb15
This commit is contained in:
Leon Scroggins
2010-08-24 14:16:09 -04:00
parent e1ab2228e2
commit fe026bdd3c
16 changed files with 549 additions and 62 deletions

View File

@@ -15782,6 +15782,23 @@
>
</field>
</class>
<class name="R.menu"
extends="java.lang.Object"
abstract="false"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
<constructor name="R.menu"
type="android.R.menu"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</constructor>
</class>
<class name="R.plurals"
extends="java.lang.Object"
abstract="false"
@@ -215998,6 +216015,19 @@
<parameter name="client" type="android.webkit.WebViewClient">
</parameter>
</method>
<method name="showFindDialog"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="text" type="java.lang.String">
</parameter>
</method>
<method name="stopLoading"
return="void"
abstract="false"

View File

@@ -209,7 +209,10 @@ public class Browser {
send.putExtra(Intent.EXTRA_TEXT, stringToSend);
try {
c.startActivity(Intent.createChooser(send, chooserDialogTitle));
Intent i = Intent.createChooser(send, chooserDialogTitle);
// In case this is called from outside an Activity
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
c.startActivity(i);
} catch(android.content.ActivityNotFoundException ex) {
// if no app handles it, do nothing
}

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2010 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 android.webkit;
import android.content.Context;
import android.content.res.Resources;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextWatcher;
import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
View.OnLongClickListener {
private View mCustomView;
private EditText mEditText;
private TextView mMatches;
private WebView mWebView;
private InputMethodManager mInput;
private Resources mResources;
private boolean mMatchesFound;
private int mNumberOfMatches;
private View mTitleBar;
FindActionModeCallback(Context context) {
mCustomView = LayoutInflater.from(context).inflate(
com.android.internal.R.layout.webview_find, null);
mEditText = (EditText) mCustomView.findViewById(
com.android.internal.R.id.edit);
// Override long click so that select ActionMode is not opened, which
// would exit find ActionMode.
mEditText.setOnLongClickListener(this);
setText("");
mMatches = (TextView) mCustomView.findViewById(
com.android.internal.R.id.matches);
mInput = (InputMethodManager)
context.getSystemService(Context.INPUT_METHOD_SERVICE);
mResources = context.getResources();
}
void setTitleBar(View v) { mTitleBar = v; }
/*
* Place text in the text field so it can be searched for. Need to press
* the find next or find previous button to find all of the matches.
*/
void setText(String text) {
mEditText.setText(text);
Spannable span = (Spannable) mEditText.getText();
int length = span.length();
// Ideally, we would like to set the selection to the whole field,
// but this brings up the Text selection CAB, which dismisses this
// one.
Selection.setSelection(span, length, length);
// Necessary each time we set the text, so that this will watch
// changes to it.
span.setSpan(this, 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
mMatchesFound = false;
}
/*
* Set the WebView to search. Must be non null, and set before calling
* startActionMode.
*/
void setWebView(WebView webView) {
if (null == webView) {
throw new AssertionError("WebView supplied to "
+ "FindActionModeCallback cannot be null");
}
mWebView = webView;
}
/*
* Move the highlight to the next match.
* @param next If true, find the next match further down in the document.
* If false, find the previous match, up in the document.
*/
private void findNext(boolean next) {
if (mWebView == null) {
throw new AssertionError(
"No WebView for FindActionModeCallback::findNext");
}
mWebView.findNext(next);
}
/*
* Highlight all the instances of the string from mEditText in mWebView.
*/
void findAll() {
if (mWebView == null) {
throw new AssertionError(
"No WebView for FindActionModeCallback::findAll");
}
CharSequence find = mEditText.getText();
if (0 == find.length()) {
mWebView.clearMatches();
mMatches.setVisibility(View.GONE);
mMatchesFound = false;
} else {
mMatchesFound = true;
mMatches.setVisibility(View.VISIBLE);
mNumberOfMatches = mWebView.findAll(find.toString());
if (0 == mNumberOfMatches) {
mMatches.setText(mResources.getString(
com.android.internal.R.string.no_matches));
} else {
updateMatchesString();
}
}
}
/*
* Update the string which tells the user how many matches were found, and
* which match is currently highlighted.
*/
private void updateMatchesString() {
String template = mResources.getQuantityString(
com.android.internal.R.plurals.matches_found, mNumberOfMatches,
mWebView.findIndex() + 1, mNumberOfMatches);
mMatches.setText(template);
}
// OnLongClickListener implementation
@Override
public boolean onLongClick(View v) { return true; }
// ActionMode.Callback implementation
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setCustomView(mCustomView);
mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
menu);
Editable edit = mEditText.getText();
Selection.setSelection(edit, edit.length());
mMatches.setVisibility(View.GONE);
mMatchesFound = false;
mMatches.setText("0");
mEditText.requestFocus();
mInput.showSoftInput(mEditText, 0);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
if (mTitleBar != null) mWebView.setEmbeddedTitleBar(mTitleBar);
mWebView.notifyFindDialogDismissed();
mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!mMatchesFound) {
findAll();
return true;
}
switch(item.getItemId()) {
case com.android.internal.R.id.find_prev:
findNext(false);
break;
case com.android.internal.R.id.find_next:
findNext(true);
break;
default:
return false;
}
updateMatchesString();
return true;
}
// TextWatcher implementation
@Override
public void beforeTextChanged(CharSequence s,
int start,
int count,
int after) {
// Does nothing. Needed to implement TextWatcher.
}
@Override
public void onTextChanged(CharSequence s,
int start,
int before,
int count) {
findAll();
}
@Override
public void afterTextChanged(Editable s) {
// Does nothing. Needed to implement TextWatcher.
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2010 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 android.webkit;
import android.provider.Browser;
import android.webkit.WebView;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
class SelectActionModeCallback implements ActionMode.Callback {
private WebView mWebView;
private View mTitleBar;
private ActionMode mActionMode;
void setWebView(WebView webView) {
mWebView = webView;
}
void setTitleBar(View v) { mTitleBar = v; }
void finish() {
mActionMode.finish();
}
// ActionMode.Callback implementation
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy,
menu);
mode.setTitle(com.android.internal.R.string.textSelectionCABTitle);
mActionMode = mode;
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch(item.getItemId()) {
case android.R.id.copy:
mWebView.copySelection();
mode.finish();
break;
case com.android.internal.R.id.share:
String selection = mWebView.getSelection();
Browser.sendString(mWebView.getContext(), selection);
mode.finish();
break;
case com.android.internal.R.id.select_all:
mWebView.selectAll();
break;
case com.android.internal.R.id.find:
String sel= mWebView.getSelection();
mode.finish();
mWebView.showFindDialog(sel);
break;
default:
return false;
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
if (mTitleBar != null) mWebView.setEmbeddedTitleBar(mTitleBar);
mWebView.selectionDone();
}
}

View File

@@ -323,23 +323,6 @@ public class WebChromeClient {
uploadFile.onReceiveValue(null);
}
/**
* Tell the client that the selection has been initiated.
* @hide
*/
public void onSelectionStart(WebView view) {
// By default we cancel the selection again, thus disabling
// text selection unless the chrome client supports it.
view.notifySelectDialogDismissed();
}
/**
* Tell the client that the selection has been copied or canceled.
* @hide
*/
public void onSelectionDone(WebView view) {
}
/**
* Tell the client that the page being viewed is web app capable,
* i.e. has specified the fullscreen-web-app-capable meta tag.

View File

@@ -2114,6 +2114,15 @@ public class WebView extends AbsoluteLayout
* @hide
*/
public void setEmbeddedTitleBar(View v) {
if (null == v) {
// If one of our callbacks is holding onto the titlebar to replace
// it when its ActionMode ends, remove it.
if (mSelectCallback != null) {
mSelectCallback.setTitleBar(null);
} else if (mFindCallback != null) {
mFindCallback.setTitleBar(null);
}
}
if (mTitleBar == v) return;
if (mTitleBar != null) {
removeView(mTitleBar);
@@ -2679,18 +2688,49 @@ public class WebView extends AbsoluteLayout
}
/**
* @hide
* Start an ActionMode for finding text in this WebView.
* @param text If non-null, will be the initial text to search for.
* Otherwise, the last String searched for in this WebView will
* be used to start.
*/
public void setFindIsUp(boolean isUp) {
public void showFindDialog(String text) {
mFindCallback = new FindActionModeCallback(mContext);
setFindIsUp(true);
mFindCallback.setWebView(this);
View titleBar = mTitleBar;
// We do not want to show the embedded title bar during find or
// select, but keep track of it so that it can be replaced when the
// mode is exited.
setEmbeddedTitleBar(null);
mFindCallback.setTitleBar(titleBar);
startActionMode(mFindCallback);
if (text == null) {
text = mLastFind;
}
if (text != null) {
mFindCallback.setText(text);
}
}
/**
* Keep track of the find callback so that we can remove its titlebar if
* necessary.
*/
private FindActionModeCallback mFindCallback;
/**
* Toggle whether the find dialog is showing, for both native and Java.
*/
private void setFindIsUp(boolean isUp) {
mFindIsUp = isUp;
if (0 == mNativeClass) return; // client isn't initialized
nativeSetFindIsUp(isUp);
}
/**
* @hide
* Return the index of the currently highlighted match.
*/
public int findIndex() {
int findIndex() {
if (0 == mNativeClass) return -1;
return nativeFindIndex();
}
@@ -2699,8 +2739,8 @@ public class WebView extends AbsoluteLayout
// or not we draw the highlights for matches.
private boolean mFindIsUp;
// Keep track of the last string sent, so we can search again after an
// orientation change or the dismissal of the soft keyboard.
// Keep track of the last string sent, so we can search again when find is
// reopened.
private String mLastFind;
/**
@@ -2757,7 +2797,6 @@ public class WebView extends AbsoluteLayout
* Clear the highlighting surrounding text matches created by findAll.
*/
public void clearMatches() {
mLastFind = "";
if (mNativeClass == 0)
return;
nativeSetFindIsEmpty();
@@ -2765,9 +2804,10 @@ public class WebView extends AbsoluteLayout
}
/**
* @hide
* Called when the find ActionMode ends.
*/
public void notifyFindDialogDismissed() {
void notifyFindDialogDismissed() {
mFindCallback = null;
if (mWebViewCore == null) {
return;
}
@@ -3359,14 +3399,18 @@ public class WebView extends AbsoluteLayout
setUpSelect();
if (mNativeClass != 0 && nativeWordSelection(x, y)) {
nativeSetExtendSelection();
WebChromeClient client = getWebChromeClient();
if (client != null) client.onSelectionStart(this);
return true;
}
notifySelectDialogDismissed();
selectionDone();
return false;
}
/**
* Keep track of the Callback so we can end its ActionMode or remove its
* titlebar.
*/
private SelectActionModeCallback mSelectCallback;
private boolean didUpdateTextViewBounds(boolean allowIntersect) {
Rect contentBounds = nativeFocusCandidateNodeBounds();
Rect vBox = contentToViewRect(contentBounds);
@@ -4113,10 +4157,7 @@ public class WebView extends AbsoluteLayout
return false;
}
/**
* @hide pending API council approval.
*/
public void setUpSelect() {
private void setUpSelect() {
if (0 == mNativeClass) return; // client isn't initialized
if (inFullScreenMode()) return;
if (mSelectingText) return;
@@ -4137,6 +4178,15 @@ public class WebView extends AbsoluteLayout
mSelectY = mScrollY + getViewHeightWithTitle() / 2;
}
nativeHideCursor();
mSelectCallback = new SelectActionModeCallback();
mSelectCallback.setWebView(this);
View titleBar = mTitleBar;
// We do not want to show the embedded title bar during find or
// select, but keep track of it so that it can be replaced when the
// mode is exited.
setEmbeddedTitleBar(null);
mSelectCallback.setTitleBar(titleBar);
startActionMode(mSelectCallback);
}
/**
@@ -4148,9 +4198,9 @@ public class WebView extends AbsoluteLayout
}
/**
* @hide pending API council approval.
* Select all of the text in this WebView.
*/
public void selectAll() {
void selectAll() {
if (0 == mNativeClass) return; // client isn't initialized
if (inFullScreenMode()) return;
if (!mSelectingText) setUpSelect();
@@ -4161,36 +4211,24 @@ public class WebView extends AbsoluteLayout
}
/**
* @hide pending API council approval.
* Called when the selection has been removed.
*/
public boolean selectDialogIsUp() {
return mSelectingText;
}
/**
* @hide pending API council approval.
*/
public void notifySelectDialogDismissed() {
mSelectingText = false;
WebViewCore.resumeUpdatePicture(mWebViewCore);
}
/**
* @hide pending API council approval.
*/
public void selectionDone() {
void selectionDone() {
if (mSelectingText) {
WebChromeClient client = getWebChromeClient();
if (client != null) client.onSelectionDone(this);
mSelectingText = false;
// finish is idempotent, so this is fine even if selectionDone was
// called by mSelectCallback.onDestroyActionMode
mSelectCallback.finish();
mSelectCallback = null;
WebViewCore.resumeUpdatePicture(mWebViewCore);
invalidate(); // redraw without selection
notifySelectDialogDismissed();
}
}
/**
* @hide pending API council approval.
* Copy the selection to the clipboard
*/
public boolean copySelection() {
boolean copySelection() {
boolean copiedSomething = false;
String selection = getSelection();
if (selection != "") {
@@ -4210,9 +4248,9 @@ public class WebView extends AbsoluteLayout
}
/**
* @hide pending API council approval.
* Returns the currently highlighted text as a string.
*/
public String getSelection() {
String getSelection() {
if (mNativeClass == 0) return "";
return nativeGetSelection();
}
@@ -6567,8 +6605,8 @@ public class WebView extends AbsoluteLayout
case FIND_AGAIN:
// Ignore if find has been dismissed.
if (mFindIsUp) {
findAll(mLastFind);
if (mFindIsUp && mFindCallback != null) {
mFindCallback.findAll();
}
break;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText android:id="@+id/edit"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:scrollHorizontally="true"
android:inputType="text"
android:hint="@string/find_on_page"
android:imeOptions="actionDone"
android:layout_marginRight="10dip"
/>
<TextView android:id="@+id/matches"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondaryInverse"
/>
</LinearLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/copy"
android:icon="@drawable/ic_menu_attachment"
android:showAsAction="always"
/>
<item android:id="@+id/share"
android:icon="@drawable/ic_menu_share"
android:showAsAction="always"
/>
<item android:id="@+id/select_all"
android:icon="@drawable/ic_menu_chat_dashboard"
android:showAsAction="always"
/>
<item android:id="@+id/find"
android:icon="@drawable/ic_menu_find"
android:showAsAction="always"
/>
</menu>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/find_prev"
android:icon="@drawable/ic_btn_find_prev"
android:showAsAction="always"
/>
<item android:id="@+id/find_next"
android:icon="@drawable/ic_btn_find_next"
android:showAsAction="always"
/>
</menu>

View File

@@ -2290,4 +2290,21 @@
<!-- Shown when the users bandwidth is reduced because of excessive data use -->
<string name="throttled_notification_title">Mobile data limit exceeded</string>
<string name="throttled_notification_message">Touch to learn more about mobile data use</string>
<!-- Displayed on the Find dialog when there are no matches [CHAR LIMIT=NONE]-->
<string name="no_matches">No matches</string>
<!-- Find dialog hint text. Also used in the menu item to open find on page [CHAR LIMIT=NONE] -->
<string name="find_on_page">Find on page</string>
<!-- Displayed on the Find dialog to display the index of the highlighted
match and total number of matches found in the current page. [CHAR LIMIT=NONE] -->
<plurals name="matches_found">
<!-- Case of one match -->
<item quantity="one">1 match</item>
<!-- Case of multiple total matches -->
<item quantity="other"><xliff:g id="index" example="2">%d</xliff:g> of <xliff:g id="total" example="137">%d</xliff:g></item>
</plurals>
</resources>