AI2 Component  (Version nb184)
File.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2020 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 package com.google.appinventor.components.runtime;
8 
9 import android.Manifest;
10 import android.util.Log;
11 
25 
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;
33 
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,
49  nonVisible = true,
50  iconName = "images/file.png")
51 @SimpleObject
52 @UsesPermissions(permissionNames = "android.permission.WRITE_EXTERNAL_STORAGE, android.permission.READ_EXTERNAL_STORAGE")
53 public class File extends AndroidNonvisibleComponent implements Component {
54  private static final int BUFFER_LENGTH = 4096;
55  private static final String LOG_TAG = "FileComponent";
56 
61  public File(ComponentContainer container) {
62  super(container.$form());
63  }
64 
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("/")) {
93  FileUtil.checkExternalStorageWriteable(); // Only check if writing to sdcard
94  }
95  Write(fileName, text, false);
96  }
97 
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("/")) {
114  FileUtil.checkExternalStorageWriteable(); // Only check if writing to sdcard
115  }
116  Write(fileName, text, true);
117  }
118 
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) {
137  form.askPermission(Manifest.permission.READ_EXTERNAL_STORAGE, new PermissionResultHandler() {
138  @Override
139  public void HandlePermissionResponse(String permission, boolean granted) {
140  if (granted) {
141  try {
142  InputStream inputStream;
143  if (fileName.startsWith("//")) {
144  inputStream = form.openAsset(fileName.substring(2));
145  } else {
146  String filepath = AbsoluteFileName(fileName);
147  Log.d(LOG_TAG, "filepath = " + filepath);
148  inputStream = FileUtil.openFile(form, filepath);
149  }
150 
151  final InputStream asyncInputStream = inputStream;
152  AsynchUtil.runAsynchronously(new Runnable() {
153  @Override
154  public void run() {
155  AsyncRead(asyncInputStream, fileName);
156  }
157  });
158  } catch (PermissionException e) {
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",
168  }
169  } else {
170  form.dispatchPermissionDeniedEvent(File.this, "ReadFrom", permission);
171  }
172  }
173  });
174  }
175 
176 
186  @SimpleFunction(description = "Deletes a file from storage. " +
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) {
192  form.askPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new PermissionResultHandler() {
193  @Override
194  public void HandlePermissionResponse(String permission, boolean granted) {
195  if (granted) {
196  if (fileName.startsWith("//")) {
197  form.dispatchErrorOccurredEvent(File.this, "DeleteFile",
199  return;
200  }
201  String filepath = AbsoluteFileName(fileName);
202  if (MediaUtil.isExternalFile(form, fileName)) {
203  if (form.isDeniedPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
204  form.dispatchPermissionDeniedEvent(File.this, "Delete",
205  new PermissionException(Manifest.permission.WRITE_EXTERNAL_STORAGE));
206  }
207  }
208  java.io.File file = new java.io.File(filepath);
209  file.delete();
210  } else {
211  form.dispatchPermissionDeniedEvent(File.this, "Delete", permission);
212  }
213  }
214  });
215  }
216 
224  private void Write(final String filename, final String text, final boolean append) {
225  if (filename.startsWith("//")) {
226  if (append) {
227  form.dispatchErrorOccurredEvent(File.this, "AppendTo",
229  } else {
230  form.dispatchErrorOccurredEvent(File.this, "SaveFile",
231  ErrorMessages.ERROR_CANNOT_WRITE_ASSET, filename);
232  }
233  return;
234  }
235  final Runnable operation = new Runnable() {
236  @Override
237  public void run() {
238  final String filepath = AbsoluteFileName(filename);
239  if (MediaUtil.isExternalFile(form, filepath)) {
240  form.assertPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
241  }
242  final java.io.File file = new java.io.File(filepath);
243 
244  if(!file.exists()){
245  try {
246  file.createNewFile();
247  } catch (IOException e) {
248  if (append) {
249  form.dispatchErrorOccurredEvent(File.this, "AppendTo",
250  ErrorMessages.ERROR_CANNOT_CREATE_FILE, filepath);
251  } else {
252  form.dispatchErrorOccurredEvent(File.this, "SaveFile",
253  ErrorMessages.ERROR_CANNOT_CREATE_FILE, filepath);
254  }
255  return;
256  }
257  }
258  try {
259  FileOutputStream fileWriter = new FileOutputStream(file, append);
260  OutputStreamWriter out = new OutputStreamWriter(fileWriter);
261  out.write(text);
262  out.flush();
263  out.close();
264  fileWriter.close();
265 
266  form.runOnUiThread(new Runnable() {
267  @Override
268  public void run() {
269  AfterFileSaved(filename);
270  }
271  });
272  } catch (IOException e) {
273  if (append) {
274  form.dispatchErrorOccurredEvent(File.this, "AppendTo",
275  ErrorMessages.ERROR_CANNOT_WRITE_TO_FILE, filepath);
276  } else {
277  form.dispatchErrorOccurredEvent(File.this, "SaveFile",
278  ErrorMessages.ERROR_CANNOT_WRITE_TO_FILE, filepath);
279  }
280  }
281  }
282  };
283  form.askPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, new PermissionResultHandler() {
284  @Override
285  public void HandlePermissionResponse(String permission, boolean granted) {
286  if (granted) {
287  AsynchUtil.runAsynchronously(operation);
288  } else {
289  form.dispatchPermissionDeniedEvent(File.this, append ? "AppendTo" : "SaveFile",
290  permission);
291  }
292  }
293  });
294  }
295 
306  private String normalizeNewLines(String s) {
307  return s.replaceAll("\r\n", "\n");
308  }
309 
310 
319  private void AsyncRead(InputStream fileInput, final String fileName) {
320  InputStreamReader input = null;
321  try {
322  input = new InputStreamReader(fileInput);
323  StringWriter output = new StringWriter();
324  char [] buffer = new char[BUFFER_LENGTH];
325  int offset = 0;
326  int length = 0;
327  while ((length = input.read(buffer, offset, BUFFER_LENGTH)) > 0) {
328  output.write(buffer, 0, length);
329  }
330 
331  // Now that we have the file as a String,
332  // normalize any line separators to avoid compatibility between Windows and Mac
333  // text files. Users can expect \n to mean a line separator regardless of how
334  // file was created. Currently only doing this for files opened locally - not files we pull
335  // from other places like URLs.
336 
337  final String text = normalizeNewLines(output.toString());
338 
339  form.runOnUiThread(new Runnable() {
340  @Override
341  public void run() {
342  GotText(text);
343  }
344  });
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);
353  } finally {
354  if (input != null) {
355  try {
356  input.close();
357  } catch (IOException e) {
358  // do nothing...
359  }
360  }
361  }
362  }
363 
369  @SimpleEvent (description = "Event indicating that the contents from the file have been read.")
370  public void GotText(String text) {
371  // invoke the application's "GotText" event handler.
372  EventDispatcher.dispatchEvent(this, "GotText", text);
373  }
374 
380  @SimpleEvent (description = "Event indicating that the contents of the file have been written.")
381  public void AfterFileSaved(String fileName) {
382  // invoke the application's "AfterFileSaved" event handler.
383  EventDispatcher.dispatchEvent(this, "AfterFileSaved", fileName);
384  }
385 
391  private String AbsoluteFileName(String filename) {
392  if (filename.startsWith("/")) {
393  return QUtil.getExternalStoragePath(form) + filename;
394  } else {
395  java.io.File dirPath;
396  if (form.isRepl()) {
397  dirPath = new java.io.File(QUtil.getReplDataPath(form, false));
398  } else {
399  dirPath = form.getFilesDir();
400  }
401  if (!dirPath.exists()) {
402  dirPath.mkdirs(); // Make sure it exists
403  }
404  return dirPath.getPath() + "/" + filename;
405  }
406  }
407 
408 }
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.annotations.SimpleFunction
Definition: SimpleFunction.java:23
com.google.appinventor.components.runtime.util.MediaUtil.isExternalFile
static boolean isExternalFile(String mediaPath)
Definition: MediaUtil.java:218
com.google.appinventor.components.runtime.util.QUtil.getExternalStoragePath
static String getExternalStoragePath(Context context, boolean forcePrivate)
Definition: QUtil.java:36
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.util.FileUtil
Definition: FileUtil.java:37
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components
com.google.appinventor.components.runtime.util.FileUtil.checkExternalStorageWriteable
static void checkExternalStorageWriteable()
Definition: FileUtil.java:561
com.google.appinventor.components.runtime.util.MediaUtil
Definition: MediaUtil.java:53
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.runtime.File
Definition: File.java:53
com.google.appinventor.components.runtime.util.QUtil
Definition: QUtil.java:18
com.google.appinventor.components.annotations.UsesPermissions
Definition: UsesPermissions.java:21
com.google.appinventor.components.runtime.PermissionResultHandler
Definition: PermissionResultHandler.java:15
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.runtime.AndroidNonvisibleComponent
Definition: AndroidNonvisibleComponent.java:17
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANNOT_WRITE_ASSET
static final int ERROR_CANNOT_WRITE_ASSET
Definition: ErrorMessages.java:181
com.google.appinventor.components.runtime.util.AsynchUtil.runAsynchronously
static void runAsynchronously(final Runnable call)
Definition: AsynchUtil.java:23
com.google.appinventor.components.runtime.errors.PermissionException
Definition: PermissionException.java:16
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANNOT_DELETE_ASSET
static final int ERROR_CANNOT_DELETE_ASSET
Definition: ErrorMessages.java:180
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google.appinventor.components.runtime.util.AsynchUtil
Definition: AsynchUtil.java:17
com.google
com
com.google.appinventor.components.runtime.util.FileUtil.openFile
static FileInputStream openFile(String fileName)
Definition: FileUtil.java:144
com.google.appinventor.components.runtime.errors
Definition: ArrayIndexOutOfBoundsError.java:7
com.google.appinventor.components.runtime.ComponentContainer.$form
Form $form()
com.google.appinventor.components.runtime.File.File
File(ComponentContainer container)
Definition: File.java:61
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_CANNOT_FIND_FILE
static final int ERROR_CANNOT_FIND_FILE
Definition: ErrorMessages.java:176
com.google.appinventor.components.annotations
com.google.appinventor