I am working on a project where I save a screenshot in a WebView. API Level is 21. On a normal homepage it works absolutely fine, but when I visit youtube and watch a video, the video player returns as a black rectangle.
What I want to do is take a screenshot whenever the user touches the screen.
When I take a screenshot with Power & Volume Button it works perfectly. Is there an Intent which takes this kind of a screenshot? Ideally the screenshot would only contain the webview, but if it's the entire screen I can work with that too.
Any help is greatly appreciated.
Cheers,
Toby
Edit: some of my code
View rootView;
ValueCallback<String> callback = new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
category1.setText(value);
}
};
String root = android.os.Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DCIM).toString(), filename, time, puretime, movementType = ""; //movementType is set according to MotionEvent.getAction()
File myDir = new File(root + "/saved_files/")
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
rootView = findViewById(android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
calendar = Calendar.getInstance();
time = String.format("%02d:%02d:%02d.%03d", calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND),
calendar.get(Calendar.MILLISECOND));
puretime = String.format("%02d%02d%02d%03d", calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND),
calendar.get(Calendar.MILLISECOND));
if (movementType.equals("down")) {
try {
filename = puretime + ".xml";
webView.saveWebArchive(myDir+filename, false, callback);
} catch (Exception e) {
e.printStackTrace();
}
}
invalidate();
return true;
}
edit2:
apparently the problem for the black rectangles is that elements as the video are processed by the gpu and for a "normal" snapshot to work the program would need to calculate every frame which is too much work.
I think this will help
View rootView = findViewById(android.R.id.content).getRootView();
rootView.setDrawingCacheEnabled(true);
Bitmap screenshot = rootView.getDrawingCache();
Let me know once you try.
UPDATE:
If I have understood your comment correctly, you were not able to change path with saveWebArchive(filename).
Then you might want to use saveWebArchive(String,boolean,ValueCallback<String> callBack).
This will save file with the given name and you can use that file name in the callback function to do whatever you want to (like saving to external storage or anything).
Check here - saveWebArchive
Related
I'm developing Android app on Android studio using Opencv library and when I try to open my app it opens then right after that it closes and displaying crash message. I'm new on mobile development
Using : OpenCV310, Android Studio 3.0,
public class ScanLicensePlateActivity extends AppCompatActivity {
protected AnylineOcrScanView scanView;
private LicensePlateResultView licensePlateResultView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Set the flag to keep the screen on (otherwise the screen may go dark during scanning)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_anyline_ocr);
String license = getString(R.string.anyline_license_key);
// Get the view from the layout
scanView = (AnylineOcrScanView) findViewById(R.id.scan_view);
// Configure the view (cutout, the camera resolution, etc.) via json
// (can also be done in xml in the layout)
scanView.setConfig(new AnylineViewConfig(this, "license_plate_view_config.json"));
// Copies given traineddata-file to a place where the core can access it.
// This MUST be called for every traineddata file that is used
// (before startScanning() is called).
// The file must be located directly in the assets directory
// (or in tessdata/ but no other folders are allowed)
scanView.copyTrainedData("tessdata/GL-Nummernschild-Mtl7_uml.traineddata",
"8ea050e8f22ba7471df7e18c310430d8");
scanView.copyTrainedData("tessdata/Arial.traineddata", "9a5555eb6ac51c83cbb76d238028c485");
scanView.copyTrainedData("tessdata/Alte.traineddata", "f52e3822cdd5423758ba19ed75b0cc32");
scanView.copyTrainedData("tessdata/deu.traineddata", "2d5190b9b62e28fa6d17b728ca195776");
// Configure the OCR for license plate scanning via a custom script file
// This is how you could add custom scripts optimized by Anyline for your use-case
AnylineOcrConfig anylineOcrConfig = new AnylineOcrConfig();
anylineOcrConfig.setCustomCmdFile("license_plates.ale");
// set the ocr config
scanView.setAnylineOcrConfig(anylineOcrConfig);
// initialize with the license and a listener
scanView.initAnyline(license, new AnylineOcrListener() {
#Override
public void onReport(String identifier, Object value) {
// Called with interesting values, that arise during processing.
// Some possibly reported values:
//
// $brightness - the brightness of the center region of the cutout as a float value
// $confidence - the confidence, an Integer value between 0 and 100
// $thresholdedImage - the current image transformed into black and white
// $sharpness - the detected sharpness value (only reported if minSharpness > 0)
}
#Override
public boolean onTextOutlineDetected(List<PointF> list) {
// Called when the outline of a possible text is detected.
// If false is returned, the outline is drawn automatically.
return false;
}
#Override
public void onResult(AnylineOcrResult result) {
// Called when a valid result is found
String results[] = result.getText().split("-");
String licensePlate = results[1];
licensePlateResultView.setLicensePlate(licensePlate);
licensePlateResultView.setVisibility(View.VISIBLE);
}
#Override
public void onAbortRun(AnylineOcrError code, String message) {
// Is called when no result was found for the current image.
// E.g. if no text was found or the result is not valid.
}
});
// disable the reporting if set to off in preferences
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(
SettingsFragment.KEY_PREF_REPORTING_ON, true)) {
// The reporting of results - including the photo of a scanned meter -
// helps us in improving our product, and the customer experience.
// However, if you wish to turn off this reporting feature, you can do it like this:
scanView.setReportingEnabled(false);
}
addLicensePlateResultView();
}
private void addLicensePlateResultView() {
RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.main_layout);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
params.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
licensePlateResultView = new LicensePlateResultView(this);
licensePlateResultView.setVisibility(View.INVISIBLE);
mainLayout.addView(licensePlateResultView, params);
licensePlateResultView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startScanning();
}
});
}
private void startScanning() {
licensePlateResultView.setVisibility(View.INVISIBLE);
// this must be called in onResume, or after a result to start the scanning again
scanView.startScanning();
}
#Override
protected void onResume() {
super.onResume();
startScanning();
}
#Override
protected void onPause() {
super.onPause();
scanView.cancelScanning();
scanView.releaseCameraInBackground();
}
#Override
public void onBackPressed() {
if (licensePlateResultView.getVisibility() == View.VISIBLE) {
startScanning();
} else {
super.onBackPressed();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
}}
source code is here.
If possible please help.
Logcat error shown here
Ideally more information regarding the error would be best i.e the opencv library version etc. Given it seems to be an Android issue, I would advise
File and issue or view issues pertaining to this error on their github page. Search for related Android errors to see if they match.
IF you cannot find a related error, file an issue there.
I play multiple images sequentially on the same SimpleDraweeView, the issue is that when submitting a new imageURI request, theSimpleDrweeView will remove the current displayed image and replace it with nothing until the URI is downloaded. So it will leave gaps in the playing sequence ( you could think of what I'm trying to do is cartoon animation using local photos). What I would like for the SimpleDrweeView to leave the current image as is until the new one is downloaded and then just swap it when it's ready.
I tried using the low-res/high-res scheme from this ticket to put the old uri as a placeholder but that didn't work (had the same effect as before).
This is what I have now:
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri /* local image */);
And this is what I tried so far (didn't work):
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder().setTapToRetryEnabled(true)
.setLowResImageRequest(ImageRequest.fromUri((Uri) draweeView.getTag())) /*naive way to test the low/high res feature*/
.setImageRequest(ImageRequest.fromUri(uri))
.setOldController(draweeView.getController())
.build();
draweeView.setTag(uri);
draweeView.setController(controller);
I am part of the Fresco team and may be able to help.
It is strange that you experience the same issue with low-res/high-res combination. If the image is currently displayed, it means it should be in the bitmap memory cache, which in turn means that it should be able to load immediately when set as a low-res image the next time you are switching to the next frame.
Are you sure that you are setting the correct uri as low-res image? (Uri) draweeView.getTag() looks kind of suspicious. I would double check that part.
If the uri is indeed correct, but the image is not in the bitmap cache anymore, it would be worth investigating why the image that is visible is not cached anymore, as we have explicit logic in place that should prevent evicting visible images. See how to track this with verbose logging here.
If all of the above fails, third options is to actually implement your own DataSource. I can help with that, but this might be somewhat involving. The basic idea is to implement a DataSource that wraps another DataSource that actually provides the image. Then you could do something like this:
// keep this instance somewhere
mMyDataSourceSupplier = new MyDataSourceSupplier();
// build controller by specifying your custom datasource supplier instead of specifying any URIs.
Fresco.newDraweeControllerBuilder()
.setDataSourceSupplier(mMyDataSourceSupplier)
.build()
// later, when you need to change the image do
mMyDataSourceSupplier.setUri(nextUri);
// this is just an outline
class MyDataSourceSupplier implements Supplier<DataSource<CloseableReference<CloseableImage>>> {
private Uri mCurrentUri;
private DataSource<CloseableReference<CloseableImage>> mCurrentDataSource;
public void setUri(Uri uri) {
mCurrentUri = uri;
if (mCurrentDatasource != null) {
mCurrentDataSource.setUri(uri);
}
}
#Override
public DataSource<CloseableReference<CloseableImage>> get() {
mCurrentDataSource = new MyDataSource();
mCurrentDataSource.setUri(uri);
return mCurrentDataSource;
}
private class MyDataSource extends AbstractDataSource<CloseableReference<CloseableImage>> {
private DataSource mUnderlyingDataSource;
#Override
protected void closeResult(#Nullable CloseableReference<CloseableImage> result) {
CloseableReference.closeSafely(result);
}
#Override
#Nullable
public CloseableReference<CloseableImage> getResult() {
return CloseableReference.cloneOrNull(super.getResult());
}
#Override
public boolean close() {
if (mUnderlyingDataSource != null) {
mUnderlyingDataSource.close();
mUnderlyingDataSource = null;
}
return super.close();
}
public void setUri(Uri uri) {
if (mUnderlyingDataSource != null) {
mUnderlyingDataSource.close();
mUnderlyingDataSource = null;
}
if (uri != null && !isClosed()) {
mUnderlyingDataSource = Fresco.getImagePipeline().fetchDecodedImage(ImageRequest.fromUri(uri), null);
mUnderlyingDataSource.subscribe(new BaseDataSubscriber {
#Override
protected void onNewResultImpl(DataSource<List<CloseableReference<CloseableImage>>> dataSource) {
MyDataSource.super.setResult(dataSource.getResult(), false);
}
});
}
}
}
}
I have a gif image (3 dots appearing and disappearing one by one) which I am trying to set on my splash screen. How to do that using just load-animation in xml? The below is the code for my splash screen.
public class MainActivity extends Activity {
private static String TAG = MainActivity.class.getName();
private static long SLEEP_TIME = 5; // Sleep for some time
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Start timer and launch main activity
IntentLauncher launcher = new IntentLauncher();
launcher.start();
}
private class IntentLauncher extends Thread {
#Override
/**
* Sleep for some time and than start new activity.
*/
public void run() {
try {
// Sleeping
Thread.sleep(SLEEP_TIME*1000);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
// Start main activity
Intent intent = new Intent(MainActivity.this, Option.class);
MainActivity.this.startActivity(intent);
MainActivity.this.finish();
}
}
}
Android doesn't natively support gifs in that way. To play a gif you can either load it in a WebView or use the Movie class.
There's a helpful Ruby script (original gist here) that will convert a gif to an animation-list xml file.
#!/usr/bin/ruby
require 'fileutils'
require 'RMagick'
require 'slop'
require 'builder'
opts = Slop.new do
banner "gif2animdraw [options]\n"
on :i, :input=, 'path to animated GIF (or directory of GIFs)', :required => true
on :o, :outdir=, 'path to root output directory', :required => true
on :d, :density=, 'density name to use for frames', :required=>true
on :oneshot, 'if set, animation does not repeat', :required => false, :default=>false
help
end
begin
opts.parse
rescue
puts opts.help
exit -1
end
if !['ldpi', 'mdpi', 'tvdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi'].include?(opts[:d])
puts "Invalid density #{opts[:d]}"
exit -1
end
glob=File.directory?(opts[:i]) ? File.join(opts[:i], '*.gif') : opts[:i]
Dir[glob].each do |gif|
input=Magick::ImageList.new(gif).coalesce
output=File.join(opts[:o], 'drawable')
output_density=File.join(opts[:o], 'drawable-'+opts[:d])
basename=File.basename(gif, '.gif').downcase.gsub(/\W/,'_')
FileUtils.mkdir_p output
FileUtils.mkdir_p output_density
Dir.glob(File.join(output_density, basename+'*.png')).each {|f| File.delete(f) }
input.write File.join(output_density, basename+'_%d.png')
builder = Builder::XmlMarkup.new(:indent=>2)
builder.tag!('animation-list',
'xmlns:android'=>'http://schemas.android.com/apk/res/android',
'android:oneshot'=>opts[:oneshot]) do
i=0
input.each do |frame|
builder.item('android:drawable'=>"#drawable/#{basename}_#{i}",
'android:duration'=>frame.delay*1000/input.ticks_per_second)
i+=1
end
end
open(File.join(output, basename+'.xml'), 'w') do |f|
f.puts builder.target!
end
end
I think using a GIF is a rather heavy solution to your problem. Have you thought about using a library such as WaitingDots.
The author uses a custom textview and text span to achieve the same result. This solution will provide you with must for room for customizing the animation.
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";
[Android Newbie alert]
I need to capture the contents of a WebView in a BitMap and I've run into a strange problem. My approach is to register a WebViewClient with the WebView, and in onPageFinished I call capturePicture. With a simple URL (e.g. http://www.yahoo.com), it works fine. In other cases, capturePicture returns a Picture with height and width values = 0. The page loads fine, either way. The actual url I have to use has quite a few url parameters and I initially thought having any parameters was the problem, but that's not the case. Here's a few sample urls with comments indicating whether it works or not:
w.loadUrl("http://www.yahoo.com"); //yes
w.loadUrl("http://search.yahoo.com/search?p=android"); // usually not???
w.loadUrl("http://www.yahoo.com?foo=bar"); // nope
w.loadUrl("http://www.google.com"); // yep
w.loadUrl("http://www.google.com?q=android"); // yep
w.loadUrl("http://www.google.com?foo=bar"); // yes
The second case is particularly frustrating as it appears to not work. However, if I run the test app with #5 first, then switching the url to #2 and running it then works.
Here's a snippet of an actual simplified test I created:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
w = new WebView(this);
w.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView webview, String url) {
Picture picture = webview.capturePicture();
Log.d("Height", "" + picture.getHeight());
Log.d("Width", "" + picture.getWidth());
Bitmap b = Bitmap.createBitmap(picture.getWidth(), picture
.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
picture.draw(c);
}
});
w.getSettings().setJavaScriptEnabled(true);
setContentView(w);
//w.loadUrl("http://www.yahoo.com"); //yes
w.loadUrl("http://search.yahoo.com/search?p=android"); // usually not???
//w.loadUrl("http://www.yahoo.com?foo=bar"); // nope
//w.loadUrl("http://www.google.com"); // yep
//w.loadUrl("http://www.google.com?q=android"); // yep
//w.loadUrl("http://www.google.com?foo=bar"); // yes
}
Has anyone run into this issue? Hopefully I'm just being an idiot and there's a simple solution or workaround?
I just visited the documentation page again.
[here][1]
"Notify the host application that a page has finished loading. This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet. To get the notification for the new Picture, use onNewPicture(WebView, Picture)."
Used Picture Listener, I tried with your sample and it works.
Hope this helps.
I just added below code to your example and removed WebViewClient
w.setPictureListener(new PictureListener(){
public void onNewPicture(WebView view, Picture picture) {
Log.d(TAG, "onNewPicture- Height"+ picture.getHeight());
Log.d(TAG, "onNewPicture- Width"+ picture.getWidth());
}
});
[1]: http://developer.android.com/reference/android/webkit/WebViewClient.html#onPageFinished(android.webkit.WebView, java.lang.String)