7 package com.google.appinventor.components.runtime;
9 import android.Manifest;
10 import android.util.Log;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.io.OutputStreamWriter;
32 import java.io.StringWriter;
41 @DesignerComponent(version = YaVersion.FILE_COMPONENT_VERSION,
42 description =
"Non-visible component for storing and retrieving files. Use this component to " +
43 "write or read files on your device. The default behaviour is to write files to the " +
44 "private data directory associated with your App. The Companion is special cased to write " +
45 "files to /sdcard/AppInventor/data to facilitate debugging. " +
46 "If the file path starts with a slash (/), then the file is created relative to /sdcard. " +
47 "For example writing a file to /myFile.txt will write the file in /sdcard/myFile.txt.",
48 category = ComponentCategory.STORAGE,
50 iconName =
"images/file.png")
52 @UsesPermissions(permissionNames =
"android.permission.WRITE_EXTERNAL_STORAGE, android.permission.READ_EXTERNAL_STORAGE")
54 private static final int BUFFER_LENGTH = 4096;
55 private static final String LOG_TAG =
"FileComponent";
62 super(container.
$form());
83 @
SimpleFunction(description =
"Saves text to a file. If the filename " +
84 "begins with a slash (/) the file is written to the sdcard. For example writing to " +
85 "/myFile.txt will write the file to /sdcard/myFile.txt. If the filename does not start " +
86 "with a slash, it will be written in the programs private data directory where it will " +
87 "not be accessible to other programs on the phone. There is a special exception for the " +
88 "AI Companion where these files are written to /sdcard/AppInventor/data to facilitate " +
89 "debugging. Note that this block will overwrite a file if it already exists." +
90 "\n\nIf you want to add content to a file use the append block.")
91 public
void SaveFile(String text, String fileName) {
92 if (fileName.startsWith(
"/")) {
95 Write(fileName, text,
false);
110 @
SimpleFunction(description =
"Appends text to the end of a file storage, creating the file if it does not exist. " +
111 "See the help text under SaveFile for information about where files are written.")
112 public
void AppendToFile(String text, String fileName) {
113 if (fileName.startsWith(
"/")) {
116 Write(fileName, text,
true);
129 @
SimpleFunction(description =
"Reads text from a file in storage. " +
130 "Prefix the filename with / to read from a specific file on the SD card. " +
131 "for instance /myFile.txt will read the file /sdcard/myFile.txt. To read " +
132 "assets packaged with an application (also works for the Companion) start " +
133 "the filename with // (two slashes). If a filename does not start with a " +
134 "slash, it will be read from the applications private storage (for packaged " +
135 "apps) and from /sdcard/AppInventor/data for the Companion.")
136 public
void ReadFrom(final String fileName) {
139 public void HandlePermissionResponse(String permission,
boolean granted) {
142 InputStream inputStream;
143 if (fileName.startsWith(
"//")) {
144 inputStream = form.openAsset(fileName.substring(2));
146 String filepath = AbsoluteFileName(fileName);
147 Log.d(LOG_TAG,
"filepath = " + filepath);
151 final InputStream asyncInputStream = inputStream;
155 AsyncRead(asyncInputStream, fileName);
159 form.dispatchPermissionDeniedEvent(
File.this,
"ReadFrom", e);
160 }
catch (FileNotFoundException e) {
161 Log.e(LOG_TAG,
"FileNotFoundException", e);
162 form.dispatchErrorOccurredEvent(
File.this,
"ReadFrom",
164 }
catch (IOException e) {
165 Log.e(LOG_TAG,
"IOException", e);
166 form.dispatchErrorOccurredEvent(
File.this,
"ReadFrom",
170 form.dispatchPermissionDeniedEvent(
File.this,
"ReadFrom", permission);
187 "Prefix the filename with / to delete a specific file in the SD card, for instance /myFile.txt. " +
188 "will delete the file /sdcard/myFile.txt. If the file does not begin with a /, then the file " +
189 "located in the programs private storage will be deleted. Starting the file with // is an error " +
190 "because assets files cannot be deleted.")
191 public
void Delete(final String fileName) {
194 public void HandlePermissionResponse(String permission,
boolean granted) {
196 if (fileName.startsWith(
"//")) {
197 form.dispatchErrorOccurredEvent(
File.this,
"DeleteFile",
201 String filepath = AbsoluteFileName(fileName);
203 if (form.isDeniedPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
204 form.dispatchPermissionDeniedEvent(
File.this,
"Delete",
208 java.io.File file =
new java.io.File(filepath);
211 form.dispatchPermissionDeniedEvent(
File.this,
"Delete", permission);
224 private void Write(
final String filename,
final String text,
final boolean append) {
225 if (filename.startsWith(
"//")) {
227 form.dispatchErrorOccurredEvent(
File.this,
"AppendTo",
230 form.dispatchErrorOccurredEvent(File.this,
"SaveFile",
231 ErrorMessages.ERROR_CANNOT_WRITE_ASSET, filename);
235 final Runnable operation =
new Runnable() {
238 final String filepath = AbsoluteFileName(filename);
239 if (MediaUtil.isExternalFile(form, filepath)) {
240 form.assertPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
242 final java.io.File file =
new java.io.File(filepath);
246 file.createNewFile();
247 }
catch (IOException e) {
249 form.dispatchErrorOccurredEvent(File.this,
"AppendTo",
250 ErrorMessages.ERROR_CANNOT_CREATE_FILE, filepath);
252 form.dispatchErrorOccurredEvent(File.this,
"SaveFile",
253 ErrorMessages.ERROR_CANNOT_CREATE_FILE, filepath);
259 FileOutputStream fileWriter =
new FileOutputStream(file, append);
260 OutputStreamWriter out =
new OutputStreamWriter(fileWriter);
266 form.runOnUiThread(
new Runnable() {
269 AfterFileSaved(filename);
272 }
catch (IOException e) {
274 form.dispatchErrorOccurredEvent(File.this,
"AppendTo",
275 ErrorMessages.ERROR_CANNOT_WRITE_TO_FILE, filepath);
277 form.dispatchErrorOccurredEvent(File.this,
"SaveFile",
278 ErrorMessages.ERROR_CANNOT_WRITE_TO_FILE, filepath);
283 form.askPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
new PermissionResultHandler() {
285 public void HandlePermissionResponse(String permission,
boolean granted) {
287 AsynchUtil.runAsynchronously(operation);
289 form.dispatchPermissionDeniedEvent(File.this, append ?
"AppendTo" :
"SaveFile",
306 private String normalizeNewLines(String s) {
307 return s.replaceAll(
"\r\n",
"\n");
319 private void AsyncRead(InputStream fileInput,
final String fileName) {
320 InputStreamReader input =
null;
322 input =
new InputStreamReader(fileInput);
323 StringWriter output =
new StringWriter();
324 char [] buffer =
new char[BUFFER_LENGTH];
327 while ((length = input.read(buffer, offset, BUFFER_LENGTH)) > 0) {
328 output.write(buffer, 0, length);
337 final String text = normalizeNewLines(output.toString());
339 form.runOnUiThread(
new Runnable() {
345 }
catch (FileNotFoundException e) {
346 Log.e(LOG_TAG,
"FileNotFoundException", e);
347 form.dispatchErrorOccurredEvent(File.this,
"ReadFrom",
348 ErrorMessages.ERROR_CANNOT_FIND_FILE, fileName);
349 }
catch (IOException e) {
350 Log.e(LOG_TAG,
"IOException", e);
351 form.dispatchErrorOccurredEvent(File.this,
"ReadFrom",
352 ErrorMessages.ERROR_CANNOT_READ_FILE, fileName);
357 }
catch (IOException e) {
369 @SimpleEvent (description =
"Event indicating that the contents from the file have been read.")
370 public
void GotText(String text) {
380 @
SimpleEvent (description =
"Event indicating that the contents of the file have been written.")
381 public
void AfterFileSaved(String fileName) {
391 private String AbsoluteFileName(String filename) {
392 if (filename.startsWith(
"/")) {
395 java.io.File dirPath;
397 dirPath =
new java.io.File(QUtil.getReplDataPath(form,
false));
399 dirPath = form.getFilesDir();
401 if (!dirPath.exists()) {
404 return dirPath.getPath() +
"/" + filename;