AI2 Component  (Version nb184)
MediaUtil.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.util;
8 
9 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
10 
11 import android.annotation.SuppressLint;
12 import android.content.Context;
13 import android.content.res.AssetFileDescriptor;
14 import android.graphics.Bitmap;
15 import android.graphics.BitmapFactory;
16 import android.graphics.Rect;
17 import android.graphics.drawable.BitmapDrawable;
18 import android.media.MediaPlayer;
19 import android.media.SoundPool;
20 import android.net.Uri;
21 import android.os.Build;
22 import android.provider.Contacts;
23 import android.util.Log;
24 import android.view.Display;
25 import android.view.WindowManager;
26 import android.widget.VideoView;
27 
31 
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.File;
35 import java.io.FileDescriptor;
36 import java.io.FileInputStream;
37 import java.io.FilterInputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.lang.reflect.Array;
41 import java.net.MalformedURLException;
42 import java.net.URI;
43 import java.net.URL;
44 import java.util.HashMap;
45 import java.util.Map;
46 import java.util.concurrent.ConcurrentHashMap;
47 
53 public class MediaUtil {
54 
55  private enum MediaSource { ASSET, REPL_ASSET, SDCARD, FILE_URL, URL, CONTENT_URI, CONTACT_URI }
56 
57  private static final String LOG_TAG = "MediaUtil";
58 
59  // tempFileMap maps cached media (assets, etc) to their respective temp files.
60  private static final Map<String, File> tempFileMap = new HashMap<String, File>();
61 
62  // this class is used by getBitmapDrawable so it can call the asynchronous version
63  // (getBitMapDrawableAsync) and await the result (blocking the UI Thread :-()
64  private static class Synchronizer<T> {
65  private volatile boolean finished = false;
66  private T result;
67  private String error;
68 
69  public synchronized void waitfor() {
70  while (!finished) {
71  try {
72  wait();
73  } catch (InterruptedException e) {
74  }
75  }
76  }
77 
78  public synchronized void wakeup(T result) {
79  finished = true;
80  this.result = result;
81  notifyAll();
82  }
83 
84  public synchronized void error(String error) {
85  finished = true;
86  this.error = error;
87  notifyAll();
88  }
89 
90  public T getResult() {
91  return result;
92  }
93 
94  public String getError() {
95  return error;
96  }
97 
98  }
99 
100  private MediaUtil() {
101  }
102 
103  static String fileUrlToFilePath(String mediaPath) throws IOException {
104  try {
105  return new File(new URL(mediaPath).toURI()).getAbsolutePath();
106  } catch (IllegalArgumentException e) {
107  throw new IOException("Unable to determine file path of file url " + mediaPath);
108  } catch (Exception e) {
109  throw new IOException("Unable to determine file path of file url " + mediaPath);
110  }
111  }
112 
133  @SuppressLint("SdCardPath")
134  private static MediaSource determineMediaSource(Form form, String mediaPath) {
135  if (mediaPath.startsWith(QUtil.getExternalStoragePath(form))
136  || mediaPath.startsWith("/sdcard/")) {
137  return MediaSource.SDCARD;
138 
139  } else if (mediaPath.startsWith("content://contacts/")) {
140  return MediaSource.CONTACT_URI;
141 
142  } else if (mediaPath.startsWith("content://")) {
143  return MediaSource.CONTENT_URI;
144  }
145 
146  try {
147  new URL(mediaPath);
148  // It's a well formed URL.
149  if (mediaPath.startsWith("file:")) {
150  return MediaSource.FILE_URL;
151  }
152 
153  return MediaSource.URL;
154 
155  } catch (MalformedURLException e) {
156  // It's not a well formed URL!
157  }
158 
159  if (form instanceof ReplForm) {
160  if (((ReplForm)form).isAssetsLoaded())
161  return MediaSource.REPL_ASSET;
162  else
163  return MediaSource.ASSET;
164  }
165 
166  return MediaSource.ASSET;
167  }
168 
180  @SuppressLint("SdCardPath")
181  @Deprecated
182  public static boolean isExternalFileUrl(String mediaPath) {
183  Log.w(LOG_TAG, "Calling deprecated version of isExternalFileUrl", new IllegalAccessException());
184  return mediaPath.startsWith("file://" + QUtil.getExternalStoragePath(Form.getActiveForm()))
185  || mediaPath.startsWith("file:///sdcard/");
186  }
187 
195  @SuppressLint("SdCardPath")
196  public static boolean isExternalFileUrl(Context context, String mediaPath) {
197  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
198  // Q doesn't allow external files
199  return false;
200  }
201  return mediaPath.startsWith("file://" + QUtil.getExternalStorageDir(context))
202  || mediaPath.startsWith("file:///sdcard");
203  }
204 
216  @SuppressLint("SdCardPath")
217  @Deprecated
218  public static boolean isExternalFile(String mediaPath) {
219  Log.w(LOG_TAG, "Calling deprecated version of isExternalFile", new IllegalAccessException());
220  return mediaPath.startsWith(QUtil.getExternalStoragePath(Form.getActiveForm()))
221  || mediaPath.startsWith("/sdcard/") || isExternalFileUrl(Form.getActiveForm(), mediaPath);
222  }
223 
231  @SuppressLint("SdCardPath")
232  public static boolean isExternalFile(Context context, String mediaPath) {
233  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
234  // Q doesn't allow external files
235  return false;
236  }
237  return mediaPath.startsWith(QUtil.getExternalStoragePath(context))
238  || mediaPath.startsWith("/sdcard/") || isExternalFileUrl(context, mediaPath);
239  }
240 
241  private static ConcurrentHashMap<String, String> pathCache = new ConcurrentHashMap<String, String>(2);
242 
243  private static String findCaseinsensitivePath(Form form, String mediaPath)
244  throws IOException{
245  if( !pathCache.containsKey(mediaPath) ){
246  String newPath = findCaseinsensitivePathWithoutCache(form, mediaPath);
247  if( newPath == null){
248  return null;
249  }
250  pathCache.put(mediaPath, newPath);
251  }
252  return pathCache.get(mediaPath);
253  }
254 
263  private static String findCaseinsensitivePathWithoutCache(Form form, String mediaPath)
264  throws IOException{
265  String[] mediaPathlist = form.getAssets().list("");
266  int l = Array.getLength(mediaPathlist);
267  for (int i=0; i<l; i++){
268  String temp = mediaPathlist[i];
269  if (temp.equalsIgnoreCase(mediaPath)){
270  return temp;
271  }
272  }
273  return null;
274  }
275 
283  private static InputStream getAssetsIgnoreCaseInputStream(Form form, String mediaPath)
284  throws IOException{
285  try {
286  return form.getAssets().open(mediaPath);
287 
288  } catch (IOException e) {
289  String path = findCaseinsensitivePath(form, mediaPath);
290  if (path == null) {
291  throw e;
292  } else {
293  return form.getAssets().open(path);
294  }
295  }
296  }
297 
298  private static InputStream openMedia(Form form, String mediaPath, MediaSource mediaSource)
299  throws IOException {
300  switch (mediaSource) {
301  case ASSET:
302  return getAssetsIgnoreCaseInputStream(form,mediaPath);
303 
304  case REPL_ASSET:
305  form.assertPermission(READ_EXTERNAL_STORAGE);
306  return new FileInputStream(new java.io.File(URI.create(form.getAssetPath(mediaPath))));
307 
308  case SDCARD:
309  form.assertPermission(READ_EXTERNAL_STORAGE);
310  return new FileInputStream(mediaPath);
311 
312  case FILE_URL:
313  if (isExternalFileUrl(form, mediaPath)) {
314  form.assertPermission(READ_EXTERNAL_STORAGE);
315  }
316  case URL:
317  return new URL(mediaPath).openStream();
318 
319  case CONTENT_URI:
320  return form.getContentResolver().openInputStream(Uri.parse(mediaPath));
321 
322  case CONTACT_URI:
323  // Open the photo for the contact.
324  InputStream is = null;
325  if (SdkLevel.getLevel() >= SdkLevel.LEVEL_HONEYCOMB_MR1) {
326  is = HoneycombMR1Util.openContactPhotoInputStreamHelper(form.getContentResolver(),
327  Uri.parse(mediaPath));
328  } else {
329  is = Contacts.People.openContactPhotoInputStream(form.getContentResolver(),
330  Uri.parse(mediaPath));
331  }
332  if (is != null) {
333  return is;
334  }
335  // There's no photo for the contact.
336  throw new IOException("Unable to open contact photo " + mediaPath + ".");
337  }
338  throw new IOException("Unable to open media " + mediaPath + ".");
339  }
340 
341  public static InputStream openMedia(Form form, String mediaPath) throws IOException {
342  return openMedia(form, mediaPath, determineMediaSource(form, mediaPath));
343  }
344 
352  public static File copyMediaToTempFile(Form form, String mediaPath)
353  throws IOException {
354  MediaSource mediaSource = determineMediaSource(form, mediaPath);
355  return copyMediaToTempFile(form, mediaPath, mediaSource);
356  }
357 
358  private static File copyMediaToTempFile(Form form, String mediaPath, MediaSource mediaSource)
359  throws IOException {
360  InputStream in = openMedia(form, mediaPath, mediaSource);
361  File file = null;
362  try {
363  file = File.createTempFile("AI_Media_", null);
364  file.deleteOnExit();
365  FileUtil.writeStreamToFile(in, file.getAbsolutePath());
366  return file;
367 
368  } catch (IOException e) {
369  if (file != null) {
370  Log.e(LOG_TAG, "Could not copy media " + mediaPath + " to temp file " +
371  file.getAbsolutePath());
372  file.delete();
373  } else {
374  Log.e(LOG_TAG, "Could not copy media " + mediaPath + " to temp file.");
375  }
376  // TODO(lizlooney) - figure out how much space is left on the SD card and log that
377  // information.
378  throw e;
379 
380  } finally {
381  in.close();
382  }
383  }
384 
385  private static File cacheMediaTempFile(Form form, String mediaPath, MediaSource mediaSource)
386  throws IOException {
387  File tempFile = tempFileMap.get(mediaPath);
388  // If the map didn't contain an entry for mediaPath, or if the temp file no longer exists,
389  // copy the file to a new temp file.
390  if (tempFile == null || !tempFile.exists()) {
391  Log.i(LOG_TAG, "Copying media " + mediaPath + " to temp file...");
392  tempFile = copyMediaToTempFile(form, mediaPath, mediaSource);
393  Log.i(LOG_TAG, "Finished copying media " + mediaPath + " to temp file " +
394  tempFile.getAbsolutePath());
395  tempFileMap.put(mediaPath, tempFile);
396  }
397  return tempFile;
398  }
399 
400  // Image related methods
401 
419  public static BitmapDrawable getBitmapDrawable(Form form, String mediaPath)
420  throws IOException {
421  if (mediaPath == null || mediaPath.length() == 0) {
422  return null;
423  }
424  final Synchronizer syncer = new Synchronizer<BitmapDrawable>();
426  @Override
427  public void onFailure(String message) {
428  syncer.error(message);
429  }
430  @Override
431  public void onSuccess(BitmapDrawable result) {
432  syncer.wakeup(result);
433  }
434  };
435  getBitmapDrawableAsync(form, mediaPath, continuation);
436  syncer.waitfor();
437  BitmapDrawable result = (BitmapDrawable) syncer.getResult();
438  if (result == null) {
439  String error = syncer.getError();
440  if (error.startsWith("PERMISSION_DENIED:")) {
441  throw new PermissionException(error.split(":")[1]);
442  } else {
443  throw new IOException(error);
444  }
445  } else {
446  return result;
447  }
448  }
449 
461  public static void getBitmapDrawableAsync(final Form form, final String mediaPath, final AsyncCallbackPair<BitmapDrawable> continuation) {
462  if (mediaPath == null || mediaPath.length() == 0) {
463  continuation.onSuccess(null);
464  return;
465  }
466 
467  final MediaSource mediaSource = determineMediaSource(form, mediaPath);
468 
469  Runnable loadImage = new Runnable() {
470  @Override
471  public void run() {
472  // Unlike other types of media, we don't cache image files from the internet to temp files.
473  // The image at a particular URL, such as an image from a web cam, may change over time.
474  // When the app says to fetch the image, we need to get the latest image, not one that we
475  // cached previously.
476 
477  Log.d(LOG_TAG, "mediaPath = " + mediaPath);
478  InputStream is = null;
479  ByteArrayOutputStream bos = new ByteArrayOutputStream();
480  byte[] buf = new byte[4096];
481  int read;
482  try {
483  // copy the input stream to an in-memory buffer
484  is = openMedia(form, mediaPath, mediaSource);
485  while ((read = is.read(buf)) > 0) {
486  bos.write(buf, 0, read);
487  }
488  buf = bos.toByteArray();
489  } catch (PermissionException e) {
490  continuation.onFailure("PERMISSION_DENIED:" + e.getPermissionNeeded());
491  return;
492  } catch(IOException e) {
493  if (mediaSource == MediaSource.CONTACT_URI) {
494  // There's no photo for this contact, return a placeholder image.
495  BitmapDrawable drawable = new BitmapDrawable(form.getResources(),
496  BitmapFactory.decodeResource(form.getResources(),
497  android.R.drawable.picture_frame, null));
498  continuation.onSuccess(drawable);
499  return;
500  }
501  Log.d(LOG_TAG, "IOException reading file.", e);
502  continuation.onFailure(e.getMessage());
503  return;
504  } finally {
505  if (is != null) {
506  try {
507  is.close();
508  } catch(IOException e) {
509  // suppress error on close
510  Log.w(LOG_TAG, "Unexpected error on close", e);
511  }
512  }
513  is = null;
514  try {
515  bos.close();
516  } catch(IOException e) {
517  // Should never fail to close a ByteArrayOutputStream
518  }
519  bos = null;
520  }
521  ByteArrayInputStream bis = new ByteArrayInputStream(buf);
522  read = buf.length;
523  buf = null;
524  try {
525  bis.mark(read);
526  BitmapFactory.Options options = getBitmapOptions(form, bis, mediaPath);
527  bis.reset();
528  BitmapDrawable originalBitmapDrawable = new BitmapDrawable(form.getResources(), decodeStream(bis, null, options));
529  // If options.inSampleSize == 1, then the image was not unreasonably large and may represent
530  // the actual size the user intended for the image. However we still have to scale it by
531  // the device density.
532  // However if we *did* sample the image to make it smaller, then that means that the image
533  // was not sized specifically for the application. In that case it makes no sense to
534  // scale it, so we don't.
535  // When we scale the image we do the following steps:
536  // 1. set the density in the returned bitmap drawable.
537  // 2. calculate scaled width and height
538  // 3. create a scaled bitmap with the scaled measures
539  // 4. create a new bitmap drawable with the scaled bitmap
540  // 5. set the density in the scaled bitmap.
541 
542  originalBitmapDrawable.setTargetDensity(form.getResources().getDisplayMetrics());
543  if ((options.inSampleSize != 1) || (form.deviceDensity() == 1.0f)) {
544  continuation.onSuccess(originalBitmapDrawable);
545  return;
546  }
547  int scaledWidth = (int) (form.deviceDensity() * originalBitmapDrawable.getIntrinsicWidth());
548  int scaledHeight = (int) (form.deviceDensity() * originalBitmapDrawable.getIntrinsicHeight());
549  Log.d(LOG_TAG, "form.deviceDensity() = " + form.deviceDensity());
550  Log.d(LOG_TAG, "originalBitmapDrawable.getIntrinsicWidth() = " + originalBitmapDrawable.getIntrinsicWidth());
551  Log.d(LOG_TAG, "originalBitmapDrawable.getIntrinsicHeight() = " + originalBitmapDrawable.getIntrinsicHeight());
552  Bitmap scaledBitmap = Bitmap.createScaledBitmap(originalBitmapDrawable.getBitmap(),
553  scaledWidth, scaledHeight, false);
554  BitmapDrawable scaledBitmapDrawable = new BitmapDrawable(form.getResources(), scaledBitmap);
555  scaledBitmapDrawable.setTargetDensity(form.getResources().getDisplayMetrics());
556  originalBitmapDrawable = null; // So it will get GC'd on the next line
557  System.gc(); // We likely used a lot of memory, so gc now.
558  continuation.onSuccess(scaledBitmapDrawable);
559  } catch(Exception e) {
560  Log.w(LOG_TAG, "Exception while loading media.", e);
561  continuation.onFailure(e.getMessage());
562  } finally {
563  if (bis != null) {
564  try {
565  bis.close();
566  } catch(IOException e) {
567  // suppress error on close
568  Log.w(LOG_TAG, "Unexpected error on close", e);
569  }
570  }
571  }
572  }
573  };
574  AsynchUtil.runAsynchronously(loadImage);
575  }
576 
577  private static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) {
578  // We wrap a FlushedInputStream around the given InputStream. This works around a problem in
579  // BitmapFactory.decodeStream where it fails to load the image if the InputStream's skip method
580  // doesn't skip the requested number of bytes.
581  return BitmapFactory.decodeStream(new FlushedInputStream(is), outPadding, opts);
582  }
583 
584  // This class comes from
585  // http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html
586  // written by Googler Gilles Debunne.
587  private static class FlushedInputStream extends FilterInputStream {
588  public FlushedInputStream(InputStream inputStream) {
589  super(inputStream);
590  }
591 
592  @Override
593  public long skip(long n) throws IOException {
594  long totalBytesSkipped = 0;
595  while (totalBytesSkipped < n) {
596  long bytesSkipped = in.skip(n - totalBytesSkipped);
597  if (bytesSkipped == 0L) {
598  if (read() < 0) {
599  break; // we reached EOF
600  } else {
601  bytesSkipped = 1; // we read one byte
602  }
603  }
604  totalBytesSkipped += bytesSkipped;
605  }
606  return totalBytesSkipped;
607  }
608  }
609 
610  private static BitmapFactory.Options getBitmapOptions(Form form, InputStream is, String mediaPath) {
611  // Get the size of the image.
612  BitmapFactory.Options options = new BitmapFactory.Options();
613  options.inJustDecodeBounds = true;
614  decodeStream(is, null, options);
615  int imageWidth = options.outWidth;
616  int imageHeight = options.outHeight;
617 
618  // Get the screen size.
619  Display display = ((WindowManager) form.getSystemService(Context.WINDOW_SERVICE)).
620  getDefaultDisplay();
621 
622  // Set the sample size so that we scale down any image that is larger than twice the
623  // width/height of the screen.
624  // The goal is to never make an image that is actually larger than the screen end up appearing
625  // smaller than the screen.
626  // int maxWidth = 2 * display.getWidth();
627  // int maxHeight = 2 * display.getHeight();
628  int maxWidth;
629  int maxHeight;
630  if (form.getCompatibilityMode()) { // Compatibility Mode
631  maxWidth = 360 * 2; // Originally used 2 times device size, continue to do so here
632  maxHeight = 420 * 2;
633  } else { // Responsive Mode
634  maxWidth = (int) (display.getWidth() / form.deviceDensity());
635  maxHeight = (int) (display.getHeight() / form.deviceDensity());
636  }
637 
638  int sampleSize = 1;
639  while ((imageWidth / sampleSize > maxWidth) && (imageHeight / sampleSize > maxHeight)) {
640  sampleSize *= 2;
641  }
642  options = new BitmapFactory.Options();
643  Log.d(LOG_TAG, "getBitmapOptions: sampleSize = " + sampleSize + " mediaPath = " + mediaPath
644  + " maxWidth = " + maxWidth + " maxHeight = " + maxHeight +
645  " display width = " + display.getWidth() + " display height = " + display.getHeight());
646  options.inSampleSize = sampleSize;
647  return options;
648  }
649 
650  // SoundPool related methods
651 
659  private static AssetFileDescriptor getAssetsIgnoreCaseAfd(Form form, String mediaPath)
660  throws IOException{
661  try {
662  return form.getAssets().openFd(mediaPath);
663 
664  } catch (IOException e) {
665  String path = findCaseinsensitivePath(form, mediaPath);
666  if (path == null){
667  throw e;
668  } else {
669  return form.getAssets().openFd(path);
670  }
671  }
672  }
673 
686  public static int loadSoundPool(SoundPool soundPool, Form form, String mediaPath)
687  throws IOException {
688  MediaSource mediaSource = determineMediaSource(form, mediaPath);
689  switch (mediaSource) {
690  case ASSET:
691  return soundPool.load(getAssetsIgnoreCaseAfd(form,mediaPath), 1);
692 
693  case REPL_ASSET:
694  form.assertPermission(READ_EXTERNAL_STORAGE);
695  return soundPool.load(QUtil.getReplAssetPath(form) + mediaPath, 1);
696 
697  case SDCARD:
698  form.assertPermission(READ_EXTERNAL_STORAGE);
699  return soundPool.load(mediaPath, 1);
700 
701  case FILE_URL:
702  if (isExternalFileUrl(form, mediaPath)) {
703  form.assertPermission(READ_EXTERNAL_STORAGE);
704  }
705  return soundPool.load(fileUrlToFilePath(mediaPath), 1);
706 
707  case CONTENT_URI:
708  case URL:
709  File tempFile = cacheMediaTempFile(form, mediaPath, mediaSource);
710  return soundPool.load(tempFile.getAbsolutePath(), 1);
711 
712  case CONTACT_URI:
713  throw new IOException("Unable to load audio for contact " + mediaPath + ".");
714  }
715 
716  throw new IOException("Unable to load audio " + mediaPath + ".");
717  }
718 
719  // MediaPlayer related methods
720 
729  public static void loadMediaPlayer(MediaPlayer mediaPlayer, Form form, String mediaPath)
730  throws IOException {
731  MediaSource mediaSource = determineMediaSource(form, mediaPath);
732  switch (mediaSource) {
733  case ASSET:
734  AssetFileDescriptor afd = getAssetsIgnoreCaseAfd(form,mediaPath);
735  try {
736  FileDescriptor fd = afd.getFileDescriptor();
737  long offset = afd.getStartOffset();
738  long length = afd.getLength();
739  mediaPlayer.setDataSource(fd, offset, length);
740  } finally {
741  afd.close();
742  }
743  return;
744 
745 
746  case REPL_ASSET:
747  form.assertPermission(READ_EXTERNAL_STORAGE);
748  mediaPlayer.setDataSource(form.getAssetPath(mediaPath));
749  return;
750 
751  case SDCARD:
752  form.assertPermission(READ_EXTERNAL_STORAGE);
753  mediaPlayer.setDataSource(mediaPath);
754  return;
755 
756  case FILE_URL:
757  if (isExternalFileUrl(form, mediaPath)) {
758  form.assertPermission(READ_EXTERNAL_STORAGE);
759  }
760  mediaPlayer.setDataSource(fileUrlToFilePath(mediaPath));
761  return;
762 
763  case URL:
764  // This works both for streaming and non-streaming.
765  // TODO(halabelson): Think about whether we could get improved
766  // performance if we did buffering control.
767  mediaPlayer.setDataSource(mediaPath);
768  return;
769 
770  case CONTENT_URI:
771  mediaPlayer.setDataSource(form, Uri.parse(mediaPath));
772  return;
773 
774  case CONTACT_URI:
775  throw new IOException("Unable to load audio or video for contact " + mediaPath + ".");
776  }
777  throw new IOException("Unable to load audio or video " + mediaPath + ".");
778  }
779 
780  // VideoView related methods
781 
793  public static void loadVideoView(VideoView videoView, Form form, String mediaPath)
794  throws IOException {
795  MediaSource mediaSource = determineMediaSource(form, mediaPath);
796  switch (mediaSource) {
797  case ASSET:
798  case URL:
799  File tempFile = cacheMediaTempFile(form, mediaPath, mediaSource);
800  videoView.setVideoPath(tempFile.getAbsolutePath());
801  return;
802 
803  case REPL_ASSET:
804  form.assertPermission(READ_EXTERNAL_STORAGE);
805  videoView.setVideoPath(form.getAssetPath(mediaPath));
806  return;
807 
808  case SDCARD:
809  form.assertPermission(READ_EXTERNAL_STORAGE);
810  videoView.setVideoPath(mediaPath);
811  return;
812 
813  case FILE_URL:
814  if (isExternalFileUrl(form, mediaPath)) {
815  form.assertPermission(READ_EXTERNAL_STORAGE);
816  }
817  videoView.setVideoPath(fileUrlToFilePath(mediaPath));
818  return;
819 
820  case CONTENT_URI:
821  videoView.setVideoURI(Uri.parse(mediaPath));
822  return;
823 
824  case CONTACT_URI:
825  throw new IOException("Unable to load video for contact " + mediaPath + ".");
826  }
827  throw new IOException("Unable to load video " + mediaPath + ".");
828  }
829 }
com.google.appinventor.components.runtime.util.MediaUtil.getBitmapDrawable
static BitmapDrawable getBitmapDrawable(Form form, String mediaPath)
Definition: MediaUtil.java:419
com.google.appinventor.components.runtime.ReplForm
Definition: ReplForm.java:62
com.google.appinventor.components.runtime.util.AsyncCallbackPair.onFailure
void onFailure(String message)
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.FileUtil
Definition: FileUtil.java:37
com.google.appinventor.components
com.google.appinventor.components.runtime.util.MediaUtil.openMedia
static InputStream openMedia(Form form, String mediaPath)
Definition: MediaUtil.java:341
com.google.appinventor.components.runtime.util.MediaUtil.loadSoundPool
static int loadSoundPool(SoundPool soundPool, Form form, String mediaPath)
Definition: MediaUtil.java:686
com.google.appinventor.components.runtime.util.MediaUtil
Definition: MediaUtil.java:53
com.google.appinventor.components.runtime.util.QUtil.getReplAssetPath
static String getReplAssetPath(Context context, boolean forcePrivate)
Definition: QUtil.java:122
com.google.appinventor.components.runtime.util.QUtil
Definition: QUtil.java:18
com.google.appinventor.components.runtime.Form.getAssetPath
String getAssetPath(String asset)
Definition: Form.java:2740
com.google.appinventor.components.runtime.util.MediaUtil.isExternalFileUrl
static boolean isExternalFileUrl(String mediaPath)
Definition: MediaUtil.java:182
com.google.appinventor.components.runtime.util.MediaUtil.loadMediaPlayer
static void loadMediaPlayer(MediaPlayer mediaPlayer, Form form, String mediaPath)
Definition: MediaUtil.java:729
com.google.appinventor.components.runtime.util.AsyncCallbackPair.onSuccess
void onSuccess(T result)
com.google.appinventor.components.runtime.util.AsynchUtil.runAsynchronously
static void runAsynchronously(final Runnable call)
Definition: AsynchUtil.java:23
com.google.appinventor.components.runtime.util.FileUtil.writeStreamToFile
static String writeStreamToFile(InputStream in, String outputFileName)
Definition: FileUtil.java:324
com.google.appinventor.components.runtime.errors.PermissionException
Definition: PermissionException.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.Map< String, File >
com.google.appinventor.components.runtime.util.MediaUtil.getBitmapDrawableAsync
static void getBitmapDrawableAsync(final Form form, final String mediaPath, final AsyncCallbackPair< BitmapDrawable > continuation)
Definition: MediaUtil.java:461
com.google.appinventor.components.runtime.errors.PermissionException.getPermissionNeeded
String getPermissionNeeded()
Definition: PermissionException.java:32
com.google.appinventor.components.runtime.util.MediaUtil.copyMediaToTempFile
static File copyMediaToTempFile(Form form, String mediaPath)
Definition: MediaUtil.java:352
com.google.appinventor.components.runtime.util.AsynchUtil
Definition: AsynchUtil.java:17
com.google
com.google.appinventor.components.runtime.util.MediaUtil.loadVideoView
static void loadVideoView(VideoView videoView, Form form, String mediaPath)
Definition: MediaUtil.java:793
com
com.google.appinventor.components.runtime.Form.assertPermission
void assertPermission(String permission)
Definition: Form.java:2607
com.google.appinventor.components.runtime.errors
Definition: ArrayIndexOutOfBoundsError.java:7
com.google.appinventor.components.runtime.util.QUtil.getExternalStorageDir
static File getExternalStorageDir(Context context)
Definition: QUtil.java:72
com.google.appinventor.components.runtime.Form.getActiveForm
static Form getActiveForm()
Definition: Form.java:2181
com.google.appinventor.components.runtime.Form
Definition: Form.java:126
com.google.appinventor.components.runtime.util.AsyncCallbackPair
Definition: AsyncCallbackPair.java:17
com.google.appinventor