Delete/Erase a particular url from Android web view history? - android

Is it possible to erase a specific url from the android's webview history? I Google a lot but was not able to find any sol, even i checked the api's documentation and i think there is no direct way to do the same.
have a look at below code:
// getting the whole list of web view's history....
WebBackForwardList list = wv.copyBackForwardList();
// getting previuous item/link
String lastUrl = list.getItemAtIndex(list.getCurrentIndex()-1).getUrl();
From 2nd line of code it is clear that it is possible to play around with history, so i think there must be some way or trick to delete a particular link from web view history.
Does it makes any sense to you guys?
Any code snippet or any link will highly be appreciated.
thanks,

Well, not sure if you actually can play around with the back forward list. I would assume it's a copy - at least that's what the name suggest.
However, you can manipulate the back stepping to some degree. In my case I exit the application already when user tries to step back from second page towards first page.
#Override
public boolean dispatchKeyEvent(KeyEvent event) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if ((action == KeyEvent.ACTION_DOWN) && myWebView.canGoBack() ) {
WebBackForwardList list = myWebView.copyBackForwardList();
String lastUrl = list.getItemAtIndex(list.getCurrentIndex()-1).getUrl().toString();
// Exit if index == 1, i.e. currently on 2nd page
if (list.getCurrentIndex() == 1) {
finish();
} else ...
You could step over also other other pages by combining stuff from above and overloading url loading:
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
view.loadUrl(url);
return true;
}

The question and answer both helped us to find the following solution for our problem.
We wanted to jump back to particular url (partialUrl), which could be anywhere in WebBackForwardList. So we are using following code to goBackOrForward, based on the partial Url passed in method by theValueString parameter. ( We wanted to call the method from any other thread so using runOnUiThread )
if (!theValueString.isEmpty()) {
final String partialUrl = theValueString;
runOnUiThread(new Runnable() {
#Override
public void run() {
// getting the whole list of web view's history....
WebBackForwardList list = myWebview.copyBackForwardList();
int index = list.getCurrentIndex();
for (int i = list.getSize() - 1; i >= 0; i--) {
String historyurl = list.getItemAtIndex(i).getUrl();
if (historyurl.contains(partialUrl)) {
myWebview.goBackOrForward(i - index);
break;
}
}
}
});
}

Related

Android WebView : Navigate to Url Hash without reloading using goBackOrForward

I want to navigate Angular single page application without reloading the webpage. On button click I'm calling following code to navigate.
String fullUrl = "https://example.com/page1";
String hashUrl = "/page1";
public void goBackInWebView(String fullUrl, String hashUrl) {
WebBackForwardList history = webview.copyBackForwardList();
int index;
int currentIndex = history.getCurrentIndex();
for (int i = 0; i < history.getSize(); i++) {
index = i - currentIndex;
if ((webview.canGoBackOrForward(1 + i)) || (webview.canGoBackOrForward(1 - i))) {
if (history.getItemAtIndex(i).getUrl().endsWith(hashUrl)) {
webview.goBackOrForward(index);
break;
} else if (i == history.getSize()) {
webview.loadUrl(fullUrl);
break;
}
} else {
//OUTER ELSE
webview.loadUrl(fullUrl);
break;
}
}
}
If the webpage is not saved in the history it will call webview.loadUrl(fullUrl); else it will load the page using webview.goBackOrForward(index);.
but is above code everytime OUTER ELSE is callling.
First of all canGoBackOrForward is used for Getting whether the page can go back or forward the given number of steps. as you mentioned in question your web page is single page so you don't need call this method. I think bellow code can solve your problem.
public void goBackInWebView(String fullUrl, String hashUrl) {
WebBackForwardList history = webview.copyBackForwardList();
if (history.getSize() == 0) {
webview.loadUrl(fullUrl);
}else {
for (int i = 0; i < history.getSize(); i++) {
if (history.getItemAtIndex(i).getUrl().endsWith(hashUrl)) {
webview.goBackOrForward(i);
break;
} else {
webview.loadUrl(fullUrl);
}
}
}
}
I find it difficult to see much relation to Android and WebView, because the problem is SPA.
Better use hashtag # navigation for SPA, because this won't request anything server-side.
The point simply is, that the back button does not matter the least, while never navigating.
One just needs to bind JavaScript, which runs whenever window.location.hash changes.

How can I use AccessibilityService to retrieve content of other apps running..?

I'm working in Accessibility Service. i need to get the app name using Accessibility Service. i have studied the documentation of the Accessibility Service in developer's of Android website. but there is no mention about getting the app name using Accessibility.
I also want to extract text from "TextViews" of the other apps(Activities) running in background. How i can do this..
I'm assuming you know how to implement an AccessibilityService.
Retrieving window content:
First register for TYPE_WINDOW_CONTENT_CHANGED events.
#Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent){
int eventType = accessibilityEvent.getEventType();
switch (eventType) {
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
AccessibilityNodeInfo rootNode = getRootInActiveWindow();
ArrayList<AccessibilityNodeInfo> textViewNodes = new ArrayList<AccessibilityNodeInfo>();
findChildViews(rootNode);
for(AccessibilityNodeInfo mNode : textViewNodes){
if(mNode.getText()==null){
return;
}
String tv1Text = mNode.getText().toString();
//do whatever you want with the text content...
}
break;
}
}
Method findChildViews() :
private void findChildViews(AccessibilityNodeInfo parentView) {
if (parentView == null || parentView.getClassName() == null || ) {
return;
}
if (childCount == 0 && (parentView.getClassName().toString().contentEquals("android.widget.TextView"))) {
textViewNodes.add(parentView);
} else {
for (int i = 0; i < childCount; i++) {
findChildViews(parentView.getChild(i));
}
}
}
}
As far as i know, there's no assured way to get the app name, but you can try fetching the text content from the event object you get from TYPE_WINDOW_STATE_CHANGED events.
Try dumping accessibilityEvent.toString() & you'll know what i'm talking about.
Also, accessibilityEvent.getPackageName() is a simple way to get package name of that app; in case you find it useful!
previous answer is missing definition of childCount
int childCount = parentView.getChildCount();

Getting "Link text" of the link displayed in WebView

I have Android WebView which displays some links as: Link1TextLink2Text Now I would like to retrieve Link1Text and Link2Text when I long press these links. I have contextMenu implemented in the code and I could successfully get the link urls (http://link1.html, http://link2.html) using HitTestResult getExtra() method but how ccan I get those link texts ?FYI, I require those link texts for implementing "Copy link text" option in the contextMenu.
To get the text of an achor link:
I. Hook a touchstart listener to every web pages in the onPageFinished() callback of WebViewClient via evaluateJavascript. like:
//Javascripts to evaluate in onPageFinished
const w=window;
w.addEventListener('touchstart',wrappedOnDownFunc);
function wrappedOnDownFunc(e){
if(e.touches.length==1){
w._touchtarget = e.touches[0].target;
}
console.log('hey touched something ' +w._touchtarget);
}
note we've saved the touch target.
II. Then implement OnLongClicklisenter for webview. use evaluateJavascript again when you long pressed on a link object:
#Override
public boolean onLongClick(View v) {
WebView.HitTestResult result = ((WebView)v).getHitTestResult();
if (null == result) return false;
int type = result.getType();
switch (type) {
case WebView.HitTestResult.SRC_ANCHOR_TYPE:
if(result.getExtra()!=null){
((WebView)v).evaluateJavascript("window._touchtarget?window._touchtarget.innerText:''", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
System.out.println("hey received link text : "+value);
}
});
}
return true;
}
return false;
}
What's more, we can even choose to select the text of the anchor element! Actually this is one of the options that samsung browser offers when you long-pressed an tag .
To achieve this, we still need that recorded touch target. Besides we need 2 new javascript methods:
function selectTouchtarget(){
var tt = w._touchtarget;
if(tt){
w._touchtarget_href = tt.getAttribute("href");
tt.removeAttribute("href");
var sel = w.getSelection();
var range = document.createRange();
range.selectNodeContents(tt);
sel.removeAllRanges();
sel.addRange(range);
}
}
function restoreTouchtarget(){
var tt = w._touchtarget;
if(tt){
tt.setAttribute("href", w._touchtarget_href);
}
}
Finnaly in the onLongClick listener, instead of just fetch the innerText, we programmatically set the selection, trigger the action menu bar, and restore the removed href attribute of our touch target.
case WebViewmy.HitTestResult.SRC_ANCHOR_TYPE:
if(result.getExtra()!=null){
WebViewmy mWebView = ((WebViewmy)v);
mWebView.evaluateJavascript("selectTouchtarget()", new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
/* bring in action mode by a fake click on the programmatically selected text. */
MotionEvent te = MotionEvent.obtain(0,0,KeyEvent.ACTION_DOWN,mWebView.lastX,mWebView.lastY,0);
mWebView.dispatchTouchEvent(te);
te.setAction(KeyEvent.ACTION_UP);
mWebView.dispatchTouchEvent(te);
te.recycle();
//if it's not delayed for a while or the href attribute is not removed, then the above code would click into
// the anchor element instead of select it's text.
/* restore href attribute */
mWebView.postDelayed(() -> mWebView.evaluateJavascript("restoreTouchtarget()", null), 100);
}
});
}
return true;
In my case, I've extended the WebView as WebViewmy to record last touched positions, lastX and lastY, in the onTouchEvent method.
Unfortunately, a clear, official way to do this is not available. Although, there are two APIs (selectText and copySelection) which are pending API council approval, that may help to do this, but they are not available at the moment.

Accessibility function implementation problems in Android

I'm developing application that views books. There is a screen (Activity) which shows a book. It has custom view, something similar to ViewSwitcher and every page is a bitmap that is rendered by a custom View.
Now I should implement accessibility function - book should be read by the phone (audio).
I've read Accessibility section here https://developer.android.com/guide/topics/ui/accessibility/index.html but it is not clear enough.
I use SupportLibrary for accessibility management and now I have this code in ViewGroup (which manages book pages). Code 1:
private class EditionPagesViewSwitcherAccessibilityDelegate extends AccessibilityDelegateCompat {
private int mPageCount;
private double[] mPageRange;
#Override
public void onInitializeAccessibilityEvent(final View host, final AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(EditionPagesViewSwitcher.class.getName());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
event.setScrollable(canScroll());
}
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED && updatePageValues()) {
event.setItemCount(mPageCount);
// we use +1 because of user friendly numbers (from 1 not 0)
event.setFromIndex((int) (mPageRange[0] + 1));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
event.setToIndex((int) (mPageRange[1] + 1));
}
}
}
#Override
public void onInitializeAccessibilityNodeInfo(final View host, final AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(EditionPagesViewSwitcher.class.getName());
info.setScrollable(canScroll());
info.setLongClickable(true);
if (canScrollForward()) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
}
if (canScrollBackward()) {
info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
}
}
#Override
public boolean performAccessibilityAction(final View host, final int action, final Bundle args) {
if (super.performAccessibilityAction(host, action, args)) {
return true;
}
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: {
if (canScrollForward()) {
showNext();
return true;
}
}
return false;
case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: {
if (canScrollBackward()) {
showPrevious();
return true;
}
}
return false;
}
return false;
}
Here is code from page view Code 2:
#Override
public void onInitializeAccessibilityEvent(final View host, final AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(EditionPageView.class.getName());
if (hasText()) {
event.getText().add(getPageRangeText());
final String trimText = mSurfaceUpdateData.getPageText().trim();
if (trimText.length() > MAX_TEXT_LENGTH) {
event.getText().add(trimText.substring(0, MAX_TEXT_LENGTH));
// event.getText().add(trimText.substring(MAX_TEXT_LENGTH, trimText.length()));
}
else {
event.getText().add(trimText);
}
}
}
#Override
public void onInitializeAccessibilityNodeInfo(final View host, final AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setClassName(EditionPageView.class.getName());
}
Because page text data loads asynchronous first time accessibility don't have any text while executes onInitializeAccessibilityEvent code. And then when data have been loaded I fire AccessibilityEvent.TYPE_VIEW_SELECTED and AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED events. Then onInitializeAccessibilityEvent executes again and phone "read" book text.
So my questions:
Is my Accessibility implementation right? May be it is design wrong? Because I didn't find any good tutorial about this feature.
Why I need to use SDK versions checks in Support implementations in Code 1? Why support implementation doesn't handle it correctly?
Is firing TYPE_VIEW_SELECTED and TYPE_VIEW_TEXT_CHANGED really needed? Or may be some other code should be implemented?
The main question. In Code 2 there is commented code line. This code statement substring text to be less then MAX_TEXT_LENGTH (it's 3800) because if text is bigger nothing is played. Nothing. Is it accessibility restriction? Any other text that is less then this value is played well.
Does anyone know where I can find any good tutorial? (yes I saw samples).
Does anyone have any custom realizations to look through?
UPDATED
Well. Here is some answers:
As I can see TYPE_VIEW_SELECTED and TYPE_VIEW_TEXT_CHANGED events are not needed if you don't want this text to be read as soon as you get it.
On Nexus 7 all large text is played well (text up to 8000 symbols), so this issue doesn't reproduce on it, but on Samsung Galaxy Tab 10.1 (Android 4.0.4) and Genymotion emulator of Tab 10.1 with Android 4.3 does. And this is strange...
4.. According to the documentation of String.substring()
The first argument you pass is the start index in the original string, the second argument is the end index in the original string.
Example:
String text = "Hello";
partOfText = text.substring(2,text.length() - 1);
partOfText equals to "llo" (the first char is index 0)
So by putting your constant MAX_TEXT_LENGTH as a first argument, it would start at index 3800 to take out the substring.
http://developer.android.com/reference/java/lang/String.html#substring(int)
You are right MAX_TEXT_LENGTH is 3800.
About your doubt,
this code:
event.getText().add(trimText.substring(MAX_TEXT_LENGTH, trimText.length()));
}
you are trying to substring "trimText" from MAX_TEXT_LENGTH to trimText.length() !
Supposing that trimText = "STACK", trimText.length() = 5, then trimText.substring(3800,5) is going to be ?
At first, this doesn't have sense, using correctly would be like this:
trimText.substring(0,2) = "ST";

In webview, tapping link opens link in new activity but still loads it in the first activity, too

I tried including the relevant bit. I'm new at this and working with existing code. I hope someone can point me in the right direction.
Basically, say my website is www.mywebsite.com (lets call this 'A' since I can only post two hyperlinks) and you decide to do a search, so now the URL looks like this:
www.mywebsite.com/?s=notes (call it 'B')
You then get the SearchPage activity with the search results. Now say I tap a link from the search results. Example:
the url above/category/easy-news/ (call it 'C')
Website 'C' should load in PageActivity (which it does) but still loads up in SearchPageActivity which was initially showing 'B'.
On top of all of that, there seems to be a 3rd PageActivity (though it doesn't look like it opens a third time). One that also loads 'C' again. As mentioned, it is still the PageActivity, though. Pressing back closes the PageActivity and shows another PageActicity with the URL 'C'.
Closing that a second time takes me back to SearchPageActivity with the same URL 'C' which was initially 'B'. Pressing back one more time takes me back to 'B' within the same activity.
I don't seem to have this issue with the MainActivity, only in the SearchPage activity. In the MainActivity, things work fine. The code below is from SearchPage activity.
Sorry for the long winded message. I hope someone can help. Thanks.
Thanks.
private boolean isAppOrGamePage(String paramString) {
if ((paramString == null)
|| paramString.length() < 24
|| (!paramString.substring(0, 24).equals(
"http://www.mywebsite.com")))
return false;
String str1 = paramString.substring(24);
String str2 = str1.substring(0, 1 + str1.indexOf('/', 2));
if ((!mAppIdentifiers.contains(str2))
&& (!mGameIdentifiers.contains(str2)))
return false;
return true;
}
public boolean shouldOverrideUrlLoading(WebView paramWebView,
String paramString) {
if (MainActivity.DEBUG)
Log.e("shouldOverride", paramString);
if (Uri.parse(paramString).getHost() != null
&& (!Uri.parse(paramString).getHost()
.equals("myspace.com"))
&& (!paramString.contains("facebook.com"))
&& (!Uri.parse(paramString).getHost()
.contains("twitter.com"))
&& (!Uri.parse(paramString).getHost()
.equals("goo.gl"))
&& (!Uri.parse(paramString).getHost().contains("bit.ly"))
&& (!Uri.parse(paramString).getHost()
.contains("plus.google.com"))
&& (!Uri.parse(paramString).getHost()
.contains("youtube.com")))
if (isAppOrGamePage(paramString)) {
final Intent intent = new Intent(SearchActivity.this,
PageActivity.class);
intent.putExtra("app_url", paramString);
startActivity(intent);
} else
return false;
return false;
}
}
Return true when you open the other activity. You need to consume that call.
Returning true tells the webview that you have handled it, and don't want it to do anything with it.

Categories

Resources