Android PDF Writer(APW) Enconding - android

I am using Android PDF Write(APW) to create a PDF, but it doesn't work with some special characters(portuguese).
mypdf.addText(170, 50, 40,"Coração");
The standard enconding is:
mypdf.setFont(StandardFonts.SUBTYPE, StandardFonts.COURIER, StandardFonts.WIN_ANSI_ENCODING);
outputToFile("helloworld.pdf",pdfcontent,"ISO-8859-1");
I'v tried
outputToFile("helloworld.pdf",pdfcontent,"UTF-8");
outputToFile("helloworld.pdf",pdfcontent,"UTF-16");
outputToFile("helloworld.pdf",pdfcontent,"Cp1252");
and didn't succeed.
Any ideas what should I do?
EDIT
The method outputToFile is defined as:
private void outputToFile(String fileName, String pdfContent, String encoding) {
File newFile = new File(Environment.getExternalStorageDirectory() + "/" + fileName);
try {
newFile.createNewFile();
try {
FileOutputStream pdfFile = new FileOutputStream(newFile);
pdfFile.write(pdfContent.getBytes(encoding));
pdfFile.close();
} catch(FileNotFoundException e) {
//
}
} catch(IOException e) {
//
}
}
The method addText is defined as:
public void addText(int leftPosition, int topPositionFromBottom, int fontSize, String text, String transformation) {
addContent(
"BT\n" +
transformation + " " + Integer.toString(leftPosition) + " " + Integer.toString(topPositionFromBottom) + " Tm\n" +
"/F" + Integer.toString(mPageFonts.size()) + " " + Integer.toString(fontSize) + " Tf\n" +
"(" + text + ") Tj\n" +
"ET\n"
);
}
Besides, I change the font color to white adding the following rawcontent:
mypdf.addRawContent("1 1 1 rg\n");
Then I come back to the black font color:
mypdf.addRawContent("0 0 0 rg\n");

I took all the information provided, wrote the following simple unit test method and ran it.
public void test19192108()
{
PDFWriter mPDFWriter = new PDFWriter(PaperSize.FOLIO_WIDTH, PaperSize.FOLIO_HEIGHT);
mPDFWriter.setFont(StandardFonts.SUBTYPE, StandardFonts.COURIER, StandardFonts.WIN_ANSI_ENCODING);
mPDFWriter.addText(170, 50, 40,"Coração");
String pdfcontent = mPDFWriter.asString();
outputToFile("helloworld19192108.pdf",pdfcontent,"ISO-8859-1");
}
(outputToFilebeing the helper method from the APW PDFWriterDemo class)
The result looks like this:
This seems pretty much to fulfill the expectations.
Thus, in whichever way it doesn't work with some special characters(portuguese) for the OP, some vital information is missing for reproducing the issue.
PS: Depending on the setup of the development environment, there might be an issue with non-ASCII characters in the source code. Thus, it might be a good idea to replace
mPDFWriter.addText(170, 50, 40,"Coração");
with
mPDFWriter.addText(170, 50, 40,"Cora\u00e7\u00e3o");
PPS: Adobe Reader after viewing a file generated like this wants to repair it. The reason is that the cross reference table is broken. The code generating entries for it is this:
public void addObjectXRefInfo(int ByteOffset, int Generation, boolean InUse) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%010d", ByteOffset));
sb.append(" ");
sb.append(String.format("%05d", Generation));
if (InUse) {
sb.append(" n ");
} else {
sb.append(" f ");
}
sb.append("\r\n");
mList.add(sb.toString());
}
(from CrossReferenceTable.java)
Counting the characters in this entry we get 10 + 1 + 5 + 3 + 2 = 21.
According to the specification, though:
Each entry shall be exactly 20 bytes long, including the end-of-line marker
(from section 7.5.4 Cross-Reference Table of ISO 32000-1)
When using (the current version of) the Android PDF Writer, you should fix this code, too.

Related

Android String.format() Returns Question Mark (??)

I have an app which downloads some files from internet. The source file name is dynamically generated depending on user selection. I use following method to create source file name. Note that fileId is an integer (1-99).
final String fileName = "file_" + String.format("%02d", fileId) + "_download.jpg";
The issue is I have seen some users are unable to download files (and of course they leave 1 start ratings :( ). When I check my server log I see some download requests came with file names like file_??_download.jpg. It looks like String.format() has returned ?? instead of two digit number.
I searched everywhere and could not find a solution for this. Can anybody tell me what's wrong with this code? I could not even re-produce this error on any of my devices.
Thanks!
You have to do it instead:
final String fileName = "file_" + String.format("%d", fileId) + "_download.jpg";
or
final String fileName = "file_" + fileId + "_download.jpg";
if you really want only the two last digits, do it:
int formattedFileId = fileId % 100;
final String fileName = "file_" + (formattedFileId < 10 ? '0' : '') + String.format("%d", formattedFileId) + "_download.jpg";
or
int formattedFileId = fileId % 100;
final String fileName = "file_" + (formattedFileId < 10 ? '0' : '') + formattedFileId + "_download.jpg";
String.format will use the digits for the default locale which are not necessarily 0-9. Use the version that permits setting a locale, and pass in Locale.ROOT.
String.format(Locale.ROOT, "%02d", fileId)

Not cheatable Google fit step counter

i have a question to Google Fit.
I am creating a step counter (oh wonder g). This i have already done so far and it not really hard.
But now we come to my problem. I am only reading the steps with the Sensor API. The issue is, i can add new data via for example the Google Fit app and it will be counted in my app too. This introduces cheating and i do not want this.
So i need to have a way to only read "device created" data and not manually added data. Is there a nice way to to this?
From the SDK documentation it is not really clear how to proceed here.
So i need to have a way to only read "device created" data and not
manually added data. Is there a nice way to to this?
You will want to use Private Custom Data Types to achieve that. Read about the different types of Fitness data you can upload to Google Fit here.
1. Public data types
Standard data types provided by the platform, like com.google.step_count.delta. Any app can read and write data of
these types. For more information, see Public Data Types.
2. Private custom data types
Custom data types defined by an specific app. Only the app that defines the data type can read and write data
of this type. For more information, see Custom Data Types.
3. Shareable data types
Custom data types submitted to the platform by an app developer. Once approved, any app can read data of a
shareable type, but only whitelisted apps as specified by the
developer can write data of that shareable type. For more information,
see Shareable Data Types.
I was able to do this with the help of this alogrithm. But remember due to Android fragmentation this code still removes some of the user's data and count it as penalty
private String dumpDataSet(DataSet dataSet, int x) {
List<String> days = new ArrayList<>();
days.add("Monday");
days.add("Tuesday");
days.add("Wednesday");
days.add("Thursday");
days.add("Friday");
days.add("Saturday");
days.add("Sunday");
String day = days.get(Math.round(x / 24));
Log.d(TAG, "\tDay: " + day);
Log.i(TAG, "Data returned for Data type: " + dataSet.getDataType().getName());
DateFormat dateFormat = getTimeInstance();
String text = "";
try {
for (DataPoint dp : dataSet.getDataPoints()) {
Log.i(TAG, "\tStepCount getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.i(TAG, "\tStepCount getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.i(TAG, "\tStepCount App Type: " + dp.getDataType().getName());
Log.i(TAG, "\tStepCount Type: " + dp.getOriginalDataSource().getType());
for (Field field : dp.getDataType().getFields()) {
Log.i(TAG, "\tField: " + field.getName() + " Value: " + dp.getValue(field));
text += dp.getValue(field);
String si[] = dp.getOriginalDataSource().getStreamIdentifier().toLowerCase().split(":");
if ((((si[si.length - 1].contains("soft")) || (si[si.length - 1].contains("step"))) && si[si.length - 1].contains("counter"))) {
totalSteps += Integer.parseInt(dp.getValue(field).toString());
Log.d(TAG, "\tStepCount" + " Added Steps -> " + dp.getValue(field) + " steps");
text += "\n\n";
} else {
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
Log.e(TAG, "\tDay = " + day + " | Hour Number = " + x + " | StepCount" + " PENALTY DEDUCTED -> " + dp.getValue(field) + " steps");
Log.e(TAG, "\tStepCount PENALTY getStreamIdentifier: " + dp.getOriginalDataSource().getStreamIdentifier());
Log.e(TAG, "\tStepCount PENALTY getStreamName: " + dp.getOriginalDataSource().getStreamName());
Log.e(TAG, "\tStepCount PENALTY App Type: " + dp.getDataType().getName());
Log.e(TAG, "\tStepCount PENALTY Type: " + dp.getOriginalDataSource().getType());
Log.e(TAG, "\tStepCount PENALTY ---------------------------------------------------------------");
}
}
}
} catch (Exception ex) {
ex.getStackTrace();
}
return text;
}
----- UPDATE -----
You can also call
DataPoint.getOriginalDataSource().getAppPackageName()
to filter out smartwatches and other apps.
I tried as suggested by Ali Shah lakhani but
DataPoint.getOriginalDataSource().getAppPackageName();
/*I also tried but could not achieve what I wanted*/
DataPoint.getOriginalDataSource().getStreamName();
DataPoint.getOriginalDataSource().getStreamIdentifier();
did not work at least for me while retrieving data. I ended up using readDailyTotalFromLocalDevice() as shown below in order to capture steps captured by device only.
Fitness.HistoryApi.readDailyTotalFromLocalDevice(mApiClient, DataType.TYPE_STEP_COUNT_DELTA).await(1, TimeUnit.MINUTES)
I cross checked the same with some of the apps that avoids manual entries in their app and the count provided by the function above is exactly the same.
Note: If a user is having multiple devices and is using the app on all of them, readDailyTotalFromLocalDevice() will have different value for each and every device since the function is responsible for returning device specific data only.

Managing data in android cache?

I'm trying to finish the 'backbone' of my app in the next 3 weeks, however, one of the few obstacles I stutter at is saving data. I've had a look at saving data internally, but there is limited tutorials from what I can find of reading and writing multiple lines to files in the apps cache directory.
Basically what I'm trying to do is save the values stored inside a fragment. This fragment resets all its values when the user clicks a button and changes text to match a page number. (A number of duplicates that contain various values.) I would do multiple fragments, however, thought it would be beneficial to use just one fragment to minimize storage space needed.
I've only got round to writing to the files, and created two methods to manage this which are then called on the click of a button. One creates these files and the other writes to them. Unfortunately I'm inexperienced using adb and could only find that the files are created, but don't know if they are being correctly written to. Is there any chance someone could review this and possibly assist with re-reading the files? Help is much appreciated.
The two methods (Warning: A great number of lines ahead):
public void createEmptyFiles() {
try {
outputTempExerciseFileE1 = File.createTempFile("temp_exercise_1",
".txt", outputTempExerciseDir);
outputTempExerciseFileE2 = File.createTempFile("temp_exercise_2",
".txt", outputTempExerciseDir);
outputTempExerciseFileE3 = File.createTempFile("temp_exercise_3",
".txt", outputTempExerciseDir);
} catch (IOException e) {
e.printStackTrace();
Log.w("rscReporter", "Encountered an error when creating empty files!");
}
}
public void writeTemporaryFiles() {
try {
if (counterAnotherExercise == 1) {
writerTemp = new FileWriter(outputTempExerciseFileE1);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
} else if (counterAnotherExercise == 2) {
writerTemp = new FileWriter(outputTempExerciseFileE2);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
} else if (counterAnotherExercise == 3) {
writerTemp = new FileWriter(outputTempExerciseFileE3);
writerTemp
.write(editTextExerciseName.getText().toString() + "\n"
+ counterNoSets + "\n" + counterRepsPerSet
+ "\n" + counterMeanRepTime + "\n"
+ counterMeanRepTimeRefined + "\n"
+ counterSetInterval);
writerTemp.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
Any of the text files should look like:
editTextExerciseName
counterNoSets
counterRepsPerSet
counterMeanRepTime
counterMeanRepTimeRefined
counterSetInterval
Where the two methods are called:
// In a switch statement as there are around 15 buttons
case R.id.button_another_exercise_foreground:
// Increases page number in fragment
counterAnotherExercise++;
// This then checks the page number and changes text
checkPageNo();
// Writing to files is called, files were created in onCreateView()
writeTemporaryFiles();
// Resets all the counters, giving the imitation it is a completely new fragment
counterReset();
// default array exercise is then set to the page number which is then displayed as title
// For example: Exercise 1, Exercise 2, Exercise 3...
textViewExerciseTitle.setText(defaultArrayExercise);
break;
I only know the basics of Java and Android, for myself this is ambitious, however, you gotta learn somewhere! Additional suggestion for saving values are welcomed.
You don't really need files as you are only writing and then reading a handful of fixed data. Use SharedPreferences like this:
to write:
PreferenceManager.getDefaultSharedPreferences(YourActivity.this).edit().putString("editTextExerciseName", "my exercise").commit();
to read:|
PreferenceManager.getDefaultSharedPreferences(YourActivity.this).getString("editTextExerciseName");

SpannableStringBuffer limited to 9,999 characters?

My app reads in large amounts of data from text files assets and displays them on-screen in a TextView. (The largest is ~450k.) I read the file in, line-by-line into a SpannableStringBuffer (since there is some metadata I remove, such as section names). This approach has worked without complaints in the two years that I've had the app on the market (over 7k active device installs), so I know that the code is reasonably correct.
However, I got a recent report from a user on a LG Lucid (LGE VS840 4G, Android 2.3.6) that the text is truncated. From log entries, my app only got 9,999 characters in the buffer. Is this a known issue with a SpannableStringBuffer? Are there other recommended ways to build a large Spannable buffer? Any suggested workarounds?
Other than keeping a separate expected length that I update each time I append to the SpannableStringBuilder, I don't even have a good way to detect the error, since the append interface returns the object, not an error!
My code that reads in the data is:
currentOffset = 0;
try {
InputStream is = getAssets().open(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
ssb.clear();
jumpOffsets.clear();
ArrayList<String> sectionNamesList = new ArrayList<String>();
sectionOffsets.clear();
int offset = 0;
while (br.ready()) {
String s = br.readLine();
if (s.length() == 0) {
ssb.append("\n");
++offset;
} else if (s.charAt(0) == '\013') {
jumpOffsets.add(offset);
String name = s.substring(1);
if (name.length() > 0) {
sectionNamesList.add(name);
sectionOffsets.add(offset);
if (showSectionNames) {
ssb.append(name);
ssb.append("\n");
offset += name.length() + 1;
}
}
} else {
if (!showNikud) {
// Remove nikud based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s. replaceAll("[\u05b0-\u05c7]", "");
}
if (!showMeteg) {
// Remove meteg based on Unicode character ranges
// Does not replace combined characters (\ufb20-\ufb4f)
// See
// http://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet
s = s.replaceAll("\u05bd", "");
}
ssb.append(s);
ssb.append("\n");
offset += s.length() + 1;
}
}
sectionNames = sectionNamesList.toArray(new String[0]);
currentFilename = filename;
Log.v(TAG, "ssb.length()=" + ssb.length() +
", daavenText.getText().length()=" +
daavenText.getText().length() +
", showNikud=" + showNikud +
", showMeteg=" + showMeteg +
", showSectionNames=" + showSectionNames +
", currentFilename=" + currentFilename
);
After looking over the interface, I plan to replace the showNikud and showMeteg cases with InputFilters.
Is this a known issue with a SpannableStringBuffer?
I see nothing in the source code to suggest a hard limit on the size of a SpannableStringBuffer. Given your experiences, my guess is that this is a problem particular to that device, due to a stupid decision by an engineer at the device manufacturer.
Any suggested workarounds?
If you are distributing through the Google Play Store, block this device in your console.
Or, don't use one massive TextView, but instead use several smaller TextView widgets in a ListView (so they can be recycled), perhaps one per paragraph. This should have the added benefit of reducing your memory footprint.
Or, generate HTML and display the content in a WebView.
After writing (and having the user run) a test app, it appears that his device has this arbitrary limit for SpannableStringBuilder, but not StringBuilder or StringBuffer. I tested a quick change to read into a StringBuilder and then create a SpannableString from the result. Unfortunately, that means that I can't create the spans until it is fully read in.
I have to consider using multiple TextView objects in a ListView, as well as using Html.FromHtml to see if that works better for my app's long term plans.

Displaying multiple lines of text and variables in an AlertDialog using setMessage()

I need to display multiple lines of text in an Alert Dialog. If I use multiple setMessage() methods, only the last setMessage is displayed, as shown below.
final AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle("Statistics:");
alertDialog.setMessage("No. of attempts: " + counter);
alertDialog.setMessage("No. of wins: " + counterpos);
alertDialog.setMessage("No. of losses: " + counterneg);
Is there a way to create a new line for each of these in the dialog? Like using \n in System.print.out(); method.
Thanks!
You can do something like this
String alert1 = "No. of attempts: " + counter;
String alert2 = "No. of wins: " + counterpos;
String alert3 = "No. of losses: " + counterneg;
alertDialog.setMessage(alert1 +"\n"+ alert2 +"\n"+ alert3);
You could just create one string of everything you want to show and add "\n" where you'd like the line breaks to be.
alertDialog.setMessage("No. of attempts: " + counter + "\n" +
"No. of wins: " + counterpos + "\n" +
"No. of losses: " + counterneg);
Or even better to use a StringBuilder:
StringBuilder sb = new StringBuilder();
sb.append("No. of attempts: " + counter);
sb.append("\n");
sb.append("No. of wins: " + counterpos);
sb.append("\n");
sb.append("No. of losses: " + counterneg);
alertDialog.setMessage(sb.toString());
And the best way to do it would be to extract the static texts into a string resource (in the strings.xml file). Use the %d (or %s if you want to insert strings rather than ints) to get the dynamic values in the right places:
<string name="alert_message">No. of attempts: %1$d\nNo. of wins: %2$d\nNo. of losses: %3$d</string>
And then in code:
String message = getString(R.string.alert_message, counter, counterpos, counterneg);
alertDialog.setMessage(message);
You can also insert newlines directly in the strings.xml file:
<string name="my_string_text">This would revert your progress.\n\n Are you sure you want to proceed?</string>
Kotlin simplifies the solution by:
chaining the calls of the set methods
using interpolated strings
as follows:
"AlertDialog.Builder
This Builder object to allow for chaining of calls to set methods
"
(https://developer.android.com/reference/android/app/AlertDialog.Builder)
fun alertDemo() {
var counter: Int = 5
var counterpos: Int = 2
var counterneg: Int = 3
val builder = AlertDialog.Builder(this)
.setTitle("Statistics:")
.setMessage("""
|number of
|
|attempts: $counter
|wins: $counterpos
|losses: $counterneg
""".trimMargin())
.show()
}
I had prepared a screen-shot of the result, but as I am new here, I appear to have learned that uploading screenshots may be restricted to higher level community peers. Or did I miss something? Thank you for enlighting (perhaps not only) myself :)
The screenshot comes in a nice format without showing the bars.
PS:
For the minimalists, due to chaining, we could even eliminate the redundant "val builder ="

Categories

Resources