I want to save my webview to a PDF file. I know that I can print the WebView with WebView.createPrintDocumentAdapter() and PrintManager.print().
But I need a way to save the PDF, that is generated internally by the PrintDocumentAdapter, directly without any user interactions, because I need the file for further processing inside my app.
Any ideas?
I realise this question is quite old now. But I have just realised how this can be sensibly done.
Essentially as per the question you can use the createPrintDocumentAdapter method mentioned above and pass the result to your own "fake" PrintManager implementation which simply overrides the onWrite method to save the output to your own file. The snippet below shows how to take any PrintDocumentAdapter and send the output from it to a file.
public void print(PrintDocumentAdapter printAdapter, final File path, final String fileName) {
printAdapter.onLayout(null, printAttributes, null, new PrintDocumentAdapter.LayoutResultCallback() {
#Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
printAdapter.onWrite(null, getOutputFile(path, fileName), new CancellationSignal(), new PrintDocumentAdapter.WriteResultCallback() {
#Override
public void onWriteFinished(PageRange[] pages) {
super.onWriteFinished(pages);
}
});
}
}, null);
}
As you can see there's quite a few nulls passed into the adapters methods but I have checked the Chromium source code and these variables are never used so the nulls are ok.
I created a blog post about how to do it here:
http://www.annalytics.co.uk/android/pdf/2017/04/06/Save-PDF-From-An-Android-WebView/
Create a custom WebViewClient (reference) and set it on your WebView.
In this WebViewClient you should override shouldOverrideUrlLoading (WebView view, String url). From here on you can download the PDF manually when it is clicked.
Related
Android : How to accomplish auto field calculations in PDF Using qoppa library.
My Usecase: In my android app, i have PDF form with three fields. If you change Field A or Field B, Field C will be recalculated to be the sum of Field A and Field B values.
Note : To create form fields and add add JavaScript you will need to have a PDF form designer such as PDF Studio (Qoppa’s desktop PDF editor) or Adobe Acrobat.
Added image shows you the pdf with javaScript Enabled calculations are performing. Opened using AdobeReader and modified FieldB value and click outside, then FieldC will be updated with Total value automatically.
When i open the same document in my android app using below snippet auto calculations are not performing. Need Support in this context...
private void openDocumentUsingQoppa() {
// Enable JavaScript
JavaScriptSettings.setJSEnabled(JavaScriptSettings.ALWAYS);
StandardFontTF.mAssetMgr = getAssets();
try {
pdfDoc = new PDFDocument(strFilePath, null);
PDFDocument.setKey("XXXXXXXXXXXX"); //, this
} catch (PDFException e) {
e.printStackTrace();
}
viewer = new QPDFNotesView(this);
viewer.setActivity(this);
setContentView(viewer);
if(pdfDoc != null) viewer.setDocument(pdfDoc);
}
I assume you are using Qoppa's QPDFNotesView which can display and fill interactive PDF forms on Android. You will need to enable JavaScript for the JavaScript calculations to happen in QPDFNotesView.
public class JavaScriptActivity extends Activity
{
public QPDFNotesView notes;
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
JavaScriptSettings.setJSEnabled(JavaScriptSettings.ALWAYS);
notes = new QPDFNotesView(this);
notes.setActivity(this);
setContentView(notes);
}
}
I am new in android development, I am create an apps to store selected webview content into sqlite database. When user select of word, custom contextual action bar will display in webview. And now I need to get selected word in WebView. Any idea on how to get selected word in WebView and return as String?
Thanks
The only way to get text selection from a WebView is based on javascript. This is not specific to the action mode, this is how WebView text selection is supposed to be retrieved according to WebView developers' point of view. They deliberately decided to not provide an API to access text selection from Java.
The solution comprise 2 approaches.
With Android API >= 19 you can use evaluateJavascript:
webview.evaluateJavascript("(function(){return window.getSelection().toString()})()",
new ValueCallback<String>()
{
#Override
public void onReceiveValue(String value)
{
Log.v(TAG, "SELECTION:" + value);
}
});
On older builds your only resort is a custom javascript interface with a single method accepting String, which you should call via webview.loadUrl passing the same thing:
webview.loadUrl("javascript:js.callback(window.getSelection().toString())");
where js is the attached javascript interface:
webview.getSettings().setJavaScriptEnabled(true);
webview.addJavascriptInterface(new WebAppInterface(), "js");
and
public class WebAppInterface
{
#JavascriptInterface
public void callback(String value)
{
Log.v(TAG, "SELECTION:" + value);
}
}
Reference:
How to get the selected text of webview in ActionMode override
I know, there are plenty of questions in regards to saving/retrieving data on here. I was doing find looking things up on my own and really thought I could manage to find my answers without having to "ask a question", but I began to wonder something that I haven't seen an answer for on here.
MY SITUATION:
Naturally, I'm making an app. Upon closing the app, I want to save a simple array of numbers (0 or 1) or boolean values as it were. Upon starting the app, I want to search for that array, if it exists, and retrieve it for use within the app.
I began placing my code into the activity in which the array would be used. But, I started wondering if I would have to copy/paste the overridden onStop() function into all of my activities? Or do I do it in the main activity and somehow link the other activities.
Basically, no matter what state/activity the app is currently on when the app is closed, I want to be able to save the array of int/bool and open it back up when the app is started.
Maybe I didn't know how to search for what I wanted, so explaining it felt like the right thing to do.
I don't mind doing more searching, but if someone would point me in the right direction at the very least, I'd be extremely grateful.
EDIT: If there's a better way to do what I want than what I described (i.e. using a different state instead of onStop(), for instance), please feel free to throw out ideas. This is my first time actually having to deal with the activities' lifecycles and I'm a bit confused even after looking through the android development tutorials. I really think they're poorly done in most cases.
When you application needs to save some persistent data you should always do it in onPause() method and rather than onStop(). Because if android OS kills your process then onStop() and onDestroy() methods are never called. Similarly retrieve data in onResume() method.
Looking at the purpose you want to fulfill, SharedPreferences is all you want.
The documentation states:
"SharePreferences provides a general framework that allows you to save
and retrieve persistent key-value pairs of primitive data types. You
can use SharedPreferences to save any primitive data: booleans,
floats, ints, longs, and strings. This data will persist across user
sessions (even if your application is killed)."
Use SharedPreference to store small amount of data or use SQLite to store large amount of data.
See this link
http://developer.android.com/guide/topics/data/data-storage.html
Serialize an object and pass it around which is more dependable than shared preferences (had lots of trouble with consistency with shared preferences):
public class SharedVariables {
public static <S extends Serializable> void writeObject(
final Context context, String key, S serializableObject) {
ObjectOutputStream objectOut = null;
try {
FileOutputStream fileOut = context.getApplicationContext().openFileOutput(key, Activity.MODE_PRIVATE);
objectOut = new ObjectOutputStream(fileOut);
objectOut.writeObject(serializableObject);
fileOut.getFD().sync();
} catch (IOException e) {
Log.e("SharedVariable", e.getMessage(), e);
} finally {
if (objectOut != null) {
try {
objectOut.close();
} catch (IOException e) {
Log.e("SharedVariable", e.getMessage(), e);
}
}
}
}
Then use a class to use:
public class Timestamps implements Serializable {
private float timestampServer;
public float getTimestampServer() {
return timestampServer;
}
public void setTimestampServer(float timestampServer) {
this.timestampServer = timestampServer;
}
}
Then wherever you want to write to the variable use:
SharedVariables.writeObject(getApplicationContext(), "Timestamps", timestampsData);
Best way to achieve that is:
create a class. Call it MySettings, or whatever suits you
in this class, define the array of ints / booleans you are going to share, as static. Create getter & setter method (property) to access that (also as static methods)
add a static load() method to MySettings that reads from SharedPreferences. When you launch the app (in your first activity or better in a subclass of Application) call MySettings.load(). This load method sets the array
add a static save() method. Public also. Now you can save from anywhere in you app. This save() method reads the array and writes in SharedPreferences
Code sample:
public class MySettings {
private static List<Integer> data;
public static void load() {
data = new ArrayList<Integer>();
// use SharedPreferences to retrieve all your data
}
public static void save() {
// save all contents from data
}
public static List<Integer> getData() {
return data;
}
public static void setData(List<Integer> data) {
MySettings.data = data;
}
}
I want to backup my internal files. These are created by My App: random number of files and random names. Like data1.xml, data7,xml, data13.xml, ....
So I do not have any fixed file list.
When MyBackupAgentHelper::onCreate is running before the onBackup(), I can easily provide the filenames by querying the files getApplicationContext().fileList();
public class MyBackupAgentHelper extends BackupAgentHelper
{
#Override
public void onCreate()
{
String[] files = getApplicationContext().fileList();
FileBackupHelper helper = new FileBackupHelper(this, files );
addHelper(FILES_BACKUP_KEY, helper);
}
...
However, if the onRestore is ready to run after an uninstall/re-install, I cannot provide the filenames in the onCreate as this time the getApplicationContext().fileList() returns empty list - obviously.
So nothing is restored :(
Is there any way to restore all files which were backuped without specifying the filenames? Just saying, "do it all".
If not, how could I use the Data Backup in this scenario?
Thanks
I just ran into the same problem. It's frustrating because FileBackupHelper does almost exactly what we want it to do.
If you look at the code for FileBackupHelper's restoreEntity function here
https://android.googlesource.com/platform/frameworks/base.git/+/android-4.2.2_r1/core/java/android/app/backup/FileBackupHelper.java
public void restoreEntity(BackupDataInputStream data) {
if (DEBUG) Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
String key = data.getKey();
if (isKeyInList(key, mFiles)) {
File f = new File(mFilesDir, key);
writeFile(f, data);
}
}
...you can see that the only reason the files aren't being written is because they're not in the list that you passed to the FileBackupHelper constructor.
My first solution was to override isKeyInList to always return true. And that actually worked, but then it struck me as odd because isKeyInList has default protection and my FileBackupHelper subclass is not in the same package. It turns out this is some sort dalvik vm bug that allows this so I wouldn't want to rely on it (see Android method with default (package) visibility overriding (shouldn't work, but does - why?) )
But then I realized I could just hold on to the array of files that I passed to the FileBackupHelper constructor and then change the first element to always be the name of the file that wanted to be created. That way it would always be found in the list.
class MyFileBackupHelper extends FileBackupHelper
{
String[] mMyFiles;
MyFileBackupHelper(Context context, String... files)
{
super(context,files);
mMyFiles = files;
}
/* boolean isKeyInList(String key, String[] list)
{
return true;
} */
public void restoreEntity(BackupDataInputStream data)
{
mMyFiles[0] = data.getKey();
super.restoreEntity(data);
}
}
Of course this also relies on FileBackupHelper keeping the same implementation where it doesn't make a copy of the Files list. I'm not exactly sure why they went to so much trouble to prevent restoring arbitrary files, and maybe they'll try to thwart this solution later. But for now, I'm calling it good!
Oh yeah, one extra detail to making my solution work is that you need to make sure there's always one file in the list when you're restoring. That way there will always be an array element 0 to replace. This is what I did in my BackupAgent
public class MyBackupAgent extends BackupAgentHelper
{
public void AddFileHelper(String files[])
{
FileBackupHelper aHelper = new MyFileBackupHelper(this,files);
addHelper("userfiles", aHelper);
}
#Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException
{
String[] anArray = GetAllUserFiles(); // I'm not including this function for brevity
AddFileHelper(anArray);
super.onBackup(oldState, data, newState);
}
#Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException
{
AddFileHelper(new String[] { "filename" } );
super.onRestore(data, appVersionCode, newState);
}
}
So you see that I don't rely on onCreate(). Instead I put the correct files in the list in onBackup and I just put one filename in the list in onRestore. Then MyFileBackupHelper replaces array element 0 in that list every time before calling the parent restoreEntity. Hopefully google will let this solution continue to work in future version of their libraries since it seems like a nice feature to have!
EDIT: You cannot backup folders - you need to individually list files in the file helper to backup those.
I realize this question is quite old, but I was running into a similar problem (wanting to back up an arbitrary set of files from a folder), and my solution was to take all of the files, and put them into a zip file, and the have the FileBackupHelper backup the zip file. For onRestore, after the .zip file gets restored, I extract the files back. This may not be the best solution, but it seems to work for me.
I want to do the following:
I want to make a very simple gallery application. So I'd like to select a path for the images and set it as a resource. I set it in String.xml.
So I have another class, which needs the selected path to load all the images from it.
class ImageHolder
{
public ImageHolder()
{
this(R.string.image_dir);
//problem is here - R.string.image_dir returns a unique int, while what I really need is the string. How can I get it...
}
public ImageHolder(String path)
{
.........standart procedure.....
}
}
Use getString(resID) but you'll need a Context Object though.
It's a Function of Context, so within an Activity you can write this.getString(R.string.image_dir); or you can skip this altogether...