Hi All,
My app creates a table of insulin doses in HTML for a nice screen view, and then optionally to an API (html2pdf.app) to create a PDF file, using the Web Component.
This works beautifully in the Companion, the file is written to the Companion's ASD and can be shared with a Sharing component no problem.
So I don't think the problem is with the API.
When I create an APK, everything works, up to the point the HTML file is sent to the API and it attempts to download it to the app's ASD. I get an error message "Error 908: The permission WRITE_EXTERNAL_STORAGE has been denied. Please enable it in the settings app." This is on both a Pixel running Android 12 beta, and a tablet running Android 11.
I looked in the APK manifest and deleted the WRITE_EXTERNAL_STORAGE, as I've seen suggested. Then the APK, when run, does ask for permission to access media and files, but then I get the same error message.
I also tried to direct the API file to the /storage/emulated/0/Download folder using the File MakeFullPath block and Shared scope, that didn't work.
So I gather this is a storage permissions problem. My question is whether anyone has a suggestion about how to do this with AI2's current version? Or if they thought the new Storage Access Framework extension would work (although I'm not sure I understand how I'd do that)?
Many thanks for any suggestions.
Taifun
October 10, 2021, 1:24am
2
This is a known bug...
As workaround set the default file scope to Legacy in the Designer Screen settings
Taifun
1 Like
Thanks! That worked perfectly. Much appreciated.
Anke
October 10, 2021, 6:48am
4
But how was it possible to remove WRITE_EXTERNAL_STORAGE in the Manifest? That could only be possible because DefaultFileScope was already set to Legacy in the Designer. Since only then WRITE_EXTERNAL_STORAGE is declared in the Manifest on devices with Android 11+.
So your statement that you removed WRITE permission from the Manifest doesn't seem to be true.
Sorry, I was incomplete in what I said. With DefaultFileScope set to App , the manifest showed:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:maxSdkVersion="29" android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
What I meant was I deleted both of those.
With DefaultFileScope now set to Legacy , the manifest shows:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Anke
October 10, 2021, 9:01am
6
Yes, because with DefaultFileScope = App WRITE permission is only declared on devices with Android < 11 (API < 29).
But that's exactly how it should be, because WRITE permission is no longer available on devices with Android 11+. So declaring WRITE on devices running Android 11+ should have no effect. In this respect, this is illogical / wrong and of course a bug.
In fact, no permission at all should be requested for this .
Right! Which is why setting the DefaultFileScope to Legacy didn't seem intuitive. But at least it works until the bug is fixed.
Hi Everybody
I have the same question.
I have a webviewer that it has 4 tables. the data on tables was filled by APP. finally i would like to create pdf by this webviewer. I use following .html file.
when I check the .html file in windows, it works but it doesn’t work when I create .apk file.
<!DOCTYPE html>
<html><head><meta charset="utf-8"/>
<style>
body{font-family:"Times New Roman";margin:15px}
.header{background:#000;color:#fff;padding:8px;text-align:center;font-weight:bold}
table{border-collapse:collapse}
.main{width:100%;table-layout:fixed}
.main th,.main td{border:1px solid #000;padding:3px;text-align:center}
.no{width:6%}
.section{margin-top:12px}
.sectiontitle{text-align:center;font-weight:bold;font-size:18px;margin:6px}
input{width:95%;border:none;border-bottom:1px solid #000;text-align:center}
.bottom{display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-top:15px}
.small{width:100%}
.small th,.small td{border:1px solid #000;padding:4px}
.small .n{width:12%}.small .a{width:22%}
</style>
</head>
<body>
<div id="examContent">
<div class="info">Student Name:<span id="StudentName"></span> Grade:<span id="Grade"></span> Date:<span id="ExamDate"></span> Duration:<span id="Duration"></span><table><tr><th colspan="2">Teacher Marking</th></tr><tr><td>T1</td><td id="Mark1"></td></tr><tr><td>T2</td><td id="Mark2"></td></tr><tr><td>T3</td><td id="Mark3"></td></tr><tr><td>T4</td><td id="Mark4"></td></tr><tr><td>Total</td><td id="TotalMark"></td></tr></table></div>
<div class="header">UCMAS INTERNATIONAL GRADING EXAMINATION (MENTAL ARITHMETIC)</div>
<div class="section">
<div class="sectiontitle">Addition / Subtraction</div>
<table class="main"><tr><th class="no">No</th><th>Table 1</th><th>Table 2</th><th>Table 3</th><th>Table 4</th><th>Table 5</th></tr><tr><td>1</td><td id="T1_1_1"></td><td id="T1_1_2"></td><td id="T1_1_3"></td><td id="T1_1_4"></td><td id="T1_1_5"></td></tr><tr><td>2</td><td id="T1_2_1"></td><td id="T1_2_2"></td><td id="T1_2_3"></td><td id="T1_2_4"></td><td id="T1_2_5"></td></tr><tr><td>3</td><td id="T1_3_1"></td><td id="T1_3_2"></td><td id="T1_3_3"></td><td id="T1_3_4"></td><td id="T1_3_5"></td></tr><tr><td>4</td><td id="T1_4_1"></td><td id="T1_4_2"></td><td id="T1_4_3"></td><td id="T1_4_4"></td><td id="T1_4_5"></td></tr><tr><td>5</td><td id="T1_5_1"></td><td id="T1_5_2"></td><td id="T1_5_3"></td><td id="T1_5_4"></td><td id="T1_5_5"></td></tr><tr><td>6</td><td id="T1_6_1"></td><td id="T1_6_2"></td><td id="T1_6_3"></td><td id="T1_6_4"></td><td id="T1_6_5"></td></tr><tr><td>7</td><td id="T1_7_1"></td><td id="T1_7_2"></td><td id="T1_7_3"></td><td id="T1_7_4"></td><td id="T1_7_5"></td></tr><tr><td>8</td><td id="T1_8_1"></td><td id="T1_8_2"></td><td id="T1_8_3"></td><td id="T1_8_4"></td><td id="T1_8_5"></td></tr><tr><td>9</td><td id="T1_9_1"></td><td id="T1_9_2"></td><td id="T1_9_3"></td><td id="T1_9_4"></td><td id="T1_9_5"></td></tr><tr><td>10</td><td id="T1_10_1"></td><td id="T1_10_2"></td><td id="T1_10_3"></td><td id="T1_10_4"></td><td id="T1_10_5"></td></tr><tr><td>Ans</td><td><input autocomplete="off" id="A1_1" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A1_2" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A1_3" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A1_4" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A1_5" inputmode="decimal" spellcheck="false" type="text"/></td></tr><tr><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td></tr></table></div>
<div class="section">
<div class="sectiontitle">Addition / Subtraction</div>
<table class="main"><tr><th class="no">No</th><th>Table 1</th><th>Table 2</th><th>Table 3</th><th>Table 4</th><th>Table 5</th></tr><tr><td>1</td><td id="T2_1_1"></td><td id="T2_1_2"></td><td id="T2_1_3"></td><td id="T2_1_4"></td><td id="T2_1_5"></td></tr><tr><td>2</td><td id="T2_2_1"></td><td id="T2_2_2"></td><td id="T2_2_3"></td><td id="T2_2_4"></td><td id="T2_2_5"></td></tr><tr><td>3</td><td id="T2_3_1"></td><td id="T2_3_2"></td><td id="T2_3_3"></td><td id="T2_3_4"></td><td id="T2_3_5"></td></tr><tr><td>4</td><td id="T2_4_1"></td><td id="T2_4_2"></td><td id="T2_4_3"></td><td id="T2_4_4"></td><td id="T2_4_5"></td></tr><tr><td>5</td><td id="T2_5_1"></td><td id="T2_5_2"></td><td id="T2_5_3"></td><td id="T2_5_4"></td><td id="T2_5_5"></td></tr><tr><td>6</td><td id="T2_6_1"></td><td id="T2_6_2"></td><td id="T2_6_3"></td><td id="T2_6_4"></td><td id="T2_6_5"></td></tr><tr><td>7</td><td id="T2_7_1"></td><td id="T2_7_2"></td><td id="T2_7_3"></td><td id="T2_7_4"></td><td id="T2_7_5"></td></tr><tr><td>8</td><td id="T2_8_1"></td><td id="T2_8_2"></td><td id="T2_8_3"></td><td id="T2_8_4"></td><td id="T2_8_5"></td></tr><tr><td>9</td><td id="T2_9_1"></td><td id="T2_9_2"></td><td id="T2_9_3"></td><td id="T2_9_4"></td><td id="T2_9_5"></td></tr><tr><td>10</td><td id="T2_10_1"></td><td id="T2_10_2"></td><td id="T2_10_3"></td><td id="T2_10_4"></td><td id="T2_10_5"></td></tr><tr><td>Ans</td><td><input autocomplete="off" id="A2_1" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A2_2" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A2_3" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A2_4" inputmode="decimal" spellcheck="false" type="text"/></td><td><input autocomplete="off" id="A2_5" inputmode="decimal" spellcheck="false" type="text"/></td></tr><tr><td> </td><td> </td><td> </td><td> </td><td> </td><td> </td></tr></table></div>
<div class="bottom">
<div>
<div class="sectiontitle">Multiplication</div>
<table class="small">
<tr><th class="n">No</th><th>Question</th><th class="a">Answer</th></tr>
<tr><td>1</td><td id="M1"></td><td><input autocomplete="off" id="AM1" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>2</td><td id="M2"></td><td><input autocomplete="off" id="AM2" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>3</td><td id="M3"></td><td><input autocomplete="off" id="AM3" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>4</td><td id="M4"></td><td><input autocomplete="off" id="AM4" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>5</td><td id="M5"></td><td><input autocomplete="off" id="AM5" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>6</td><td id="M6"></td><td><input autocomplete="off" id="AM6" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>7</td><td id="M7"></td><td><input autocomplete="off" id="AM7" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>8</td><td id="M8"></td><td><input autocomplete="off" id="AM8" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>9</td><td id="M9"></td><td><input autocomplete="off" id="AM9" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>10</td><td id="M10"></td><td><input autocomplete="off" id="AM10" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr></table></div><div><div class="sectiontitle">Division</div><table class="small"><tr><th class="n">No</th><th>Question</th><th class="a">Answer</th></tr><tr><td>1</td><td id="D1"></td><td><input autocomplete="off" id="AD1" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>2</td><td id="D2"></td><td><input autocomplete="off" id="AD2" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>3</td><td id="D3"></td><td><input autocomplete="off" id="AD3" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>4</td><td id="D4"></td><td><input autocomplete="off" id="AD4" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>5</td><td id="D5"></td><td><input autocomplete="off" id="AD5" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>6</td><td id="D6"></td><td><input autocomplete="off" id="AD6" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>7</td><td id="D7"></td><td><input autocomplete="off" id="AD7" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>8</td><td id="D8"></td><td><input autocomplete="off" id="AD8" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>9</td><td id="D9"></td><td><input autocomplete="off" id="AD9" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr><tr><td>10</td><td id="D10"></td><td><input autocomplete="off" id="AD10" inputmode="numeric" spellcheck="false" step="1" type="number"/></td></tr></table></div></div>
<script>
function sendExamAnswers(){
let result = {
table1: [],
table2: [],
table3: [],
table4: []
};
// --------------------------
// Table 1
// --------------------------
for(let i=1;i<=5;i++){
result.table1.push(
document.getElementById("A1_"+i).value
);
}
// --------------------------
// Table 2
// --------------------------
for(let i=1;i<=5;i++){
result.table2.push(
document.getElementById("A2_"+i).value
);
}
// --------------------------
// Table 3
// --------------------------
for(let i=1;i<=10;i++){
result.table3.push(
document.getElementById("AM"+i).value
);
}
// --------------------------
// Table 4
// --------------------------
for(let i=1;i<=10;i++){
result.table4.push(
document.getElementById("AD"+i).value
);
}
window.AppInventor.setWebViewString(
JSON.stringify(result)
);
}
function downloadPDF(){
let element=document.getElementById("examContent");
let opt={
margin:0.5,
filename:"Exam.pdf",
image:{
type:'jpeg',
quality:1
},
html2canvas:{
scale:2
},
jsPDF:{
unit:'in',
format:'a4',
orientation:'portrait'
}
};
html2pdf().set(opt).from(element).save();
}
function PrintPage(){
window.print();
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<div style="text-align:center;margin:20px;">
<button onclick="downloadPDF()"
style="
padding:12px 30px;
font-size:18px;
background:#1976d2;
color:white;
border:none;
border-radius:8px;
cursor:pointer;">
Download PDF
</button>
</div>
</div>
</body>
</html>
I Pasted the html file in my request, but i don't know why it didn't send.
I uploaded it in somewhere and send the link here:
file
TIMAI2
July 7, 2026, 10:00pm
13
If using the native webviewer component, you will need an extension -> webviewextra in order to facilitate a download.
Tested and working, but you do not get a notification or toast with the download.
Example blocks, using html file from assets:
Taifun
July 8, 2026, 6:50am
14
Because it must be formatted accordingly to apoear
I did this now for you
Taifun
Thank you so much.
I use your prepare method, when I click on "Download PDF" button, two messages appear:
got base64 from blob
after this message:
2) Downloading blob to file
but I can't find the downloaded file in phone.
Where it downloaded?
TIMAI2
July 14, 2026, 5:36pm
17
See here for filename format:
blobs can only be downloaded to the Download directory (now fixed in V2.3)
To get the file, the html has to be on a server - somewhere (although in more recent testing, things did appeaar to be working correctly for a local (in assets) html file on companion)
I used my text-doc-saf extension to read the file, the download happens "outside" the app so the app does not have any rights to view the file, unless you use saf. media files may behave differently.
html:
<!DOCTYPE html>
<html lang="en…
also note that a download may not work for an html file in the assets, it may need to be in an online server.