newtonandebe / opendatakit

Automatically exported from code.google.com/p/opendatakit
0 stars 0 forks source link

Recording Audio or Video does not work #162

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1.  Create a form that requests audio or video
2.  Use the form
3.  App will let you create audio or video, but the app crashes when 
(apparently) attaching the file to the form.

What is the expected output? What do you see instead?
I would expect that the app would attach the audio or video requested to the 
form.  Instead, the app crashes.

What version of the product are you using? On what operating system?
I am using ODK Collect 1.1.5, and Google's Appspot ODK Aggregate Server (on 
their site).  My phone is an HTC Incredible running Android 2.2.  This problem 
occurs on other phones as well, but I do not know the models.  The exact error 
I get is:

android.database.CursorIndexOutOfBoundsException in AbstractCursor.java
Source method:  checkPosition
Line number: 580
Stack Trace:
java.lang.RuntimeException:  Failure delivering result ResultInfo{who=null, 
request=3, result=-1, data=Intent 
{dat=content://media.phoneStorage/audio/media/11}} to activity 
{org.odk.collect.android/org.odk.collect.android.activities.FormEntryActivity}:a
ndorid.database.CusorIndexOutOfBoundsException:  Index 0 requested, with a size 
of 0

Original issue reported on code.google.com by bill.wa...@gmail.com on 20 Dec 2010 at 2:29

GoogleCodeExporter commented 9 years ago

Original comment by wbrune...@gmail.com on 20 Dec 2010 at 8:18

GoogleCodeExporter commented 9 years ago
The problem is caused by the HTC Incredible having two partitions on the 
internal sdcard.  HTC decided to use a new unpublished Uri 
(content://media.phoneStorage/audio/media or 
content://media.phoneStorage/video/media).  The regular directories that the 
rest of Android uses for media storage are not really there.

The first real issue occurs in the AudioWidget and the VideoWidget in the 
method setBinaryData.  if (!f.renameTo(newFile)) { fails because it is trying 
to move and rename a file from two partitions across the same mnt point.  The 
docs for File.renameTo even say this will fail.

To fix this in our code we created a new method in the FileUtils class to copy 
files and delete the original.  We then replaced:

if (!f.renameTo(newFile)) {

with this code:

// new file to copy into
File newFile = new File(s);

if (!FileUtils.copyFileAndDelete(f, newFile)) {

Here is the code to do that:

/**
     * Many high end phones are now partitioning the sdcard for storage the method
     * File.renameTo(x) is failing because the partitions are on the same mnt point.
     * This method is called to fix the issue.
     * 
     * @param inFile    this is the file to be copied and deleted
     * @param outFile   this is the final file in new loacation
     * 
     * @return  true if te file was copied and deleted oe false if it fails
     */
    public static boolean copyFileAndDelete(File inFile, File outFile) {

        Log.d(t, "-------------------------------------- starting copyFileAndDelete");

        try {

            // Get the size of the file
            long length = inFile.length();
            if (length > Integer.MAX_VALUE) {
                Log.e(t, "File " + inFile.getName() + "is too large");
                return false;
            }

            int fileLength = (int)length;

            Log.d(t, "-------------------------------------- fileLength: " + fileLength);

            // get the input stream to read from
            InputStream is = null;
            is = new FileInputStream(inFile);

            // get the output steam to write to
            OutputStream os = null;
            os = new FileOutputStream(outFile);

            // Read in the bytes
            int offset = 0;
            int read = 0;

            byte[] dataBlock = new byte[fileLength];

            try {
                // read the file in
                while (offset < fileLength) {

                    read = is.read(dataBlock, offset, fileLength);

                    Log.d(t, "-------------------------------------- read: " + read);

                    // are we at end of file
                    if (read == -1) {
                        offset = fileLength;
                    } else {
                        offset += read;
                    }

                }

                // write all data to the outFile
                os.write(dataBlock, 0, fileLength);

            } catch (IOException e) {
                Log.e(t, "Cannot read " + inFile.getName());
                e.printStackTrace();
                return false;
            }

            // close the streams
            try {
                os.flush();
                os.close();
            } catch (Exception ignore) {}

            try {
                is.close();
            } catch (Exception ignore) {}

            // Ensure all the bytes have been read in
            if (offset < length) {
                Log.e(t, "Cannot copy " + inFile.getName());
                return false;
            }

            // last thing to do is delete the infile
            inFile.delete();

        }
        catch (Exception ignore) {
            ignore.printStackTrace();
            return false;
        }

        // how big is the output file
        Log.d(t, "-------------------------------------- outFile size: " + outFile.length());

        return true;

    } // end copyFileAndDelete

Original comment by stevepot...@gmail.com on 22 Dec 2010 at 2:32

GoogleCodeExporter commented 9 years ago
The second problem you will run into with the HTC Incredible is in the method 
getUriFromPath in both the AudioWidget and the Video Widget.  Because HTC is 
using unpublished Uri's you will have to trap for them with code like this:

// new Uri for HTC phonestorage
private Uri mPhoneStorageUri;

...

in method initialize

// create the uri
mPhoneStorageUri = Uri.parse("content://media/phoneStorage/audio/media");

now modify the method getUriFromPath

the current code looks like this:

Cursor c =
                getContext().getContentResolver().query(mExternalUri, null, "_data='" + path + "'",
                    null, null);
            c.moveToFirst();

            // create uri from path

            Log.d(t, "----------------------------- _id: " + c.getColumnIndex("_id"));

            newPath += c.getInt(c.getColumnIndex("_id"));

            Log.d(t, "----------------------------- newPath: " + newPath);

            c.close();

the new code would look like this:

        String newPath = mExternalUri + "/";

        boolean tryInternal = false;

        // try external first
        try {
            Cursor c =
                getContext().getContentResolver().query(mExternalUri, null, "_data='" + path + "'",
                    null, null);
            c.moveToFirst();

            // create uri from path

            Log.d(t, "----------------------------- _id: " + c.getColumnIndex("_id"));

            newPath += c.getInt(c.getColumnIndex("_id"));

            Log.d(t, "----------------------------- newPath: " + newPath);

            c.close();
        } catch (Exception ignore) {
            tryInternal = true;
        }

        // try the phone storage
        if (tryInternal) {

            try {
                Cursor c =
                    getContext().getContentResolver().query(mPhoneStorageUri, null, "_data='" + path + "'",
                        null, null);
                c.moveToFirst();

                // create uri from path

                Log.d(t, "----------------------------- phone _id: " + c.getColumnIndex("_id"));

                newPath = mPhoneStorageUri + "/" + c.getInt(c.getColumnIndex("_id"));

                Log.d(t, "-----------------------------phone newPath: " + newPath);

                c.close();

                tryInternal = false;
            } catch (Exception ignore) {
                Log.d(t, "----------------------------- mPhoneStorageUri failed");
            }
        }

With these changes and the changes from the comment above your HTC will now 
work and the code will continue to work on other phones.

Original comment by stevepot...@gmail.com on 22 Dec 2010 at 2:39

GoogleCodeExporter commented 9 years ago
The last thing you will need to change to add the mime type for amr to the 
InstanceUploaderTask.  The HTC Incredible records amr files for audio and the 
InstanceUploaderTask does not know how to handle them.

add this code to the do in background method where // mime post are being 
handled

                 else if (f.getName().endsWith(".amr")) {
                     fb = new FileBody(f, "audio/amr");
                     entity.addPart(f.getName(), fb);
                     Log.i(t, "added audio file " + f.getName());
                 }

now your audio files will be uploaded to the server

Original comment by stevepot...@gmail.com on 22 Dec 2010 at 2:43

GoogleCodeExporter commented 9 years ago

Original comment by wbrune...@gmail.com on 22 Dec 2010 at 3:54

GoogleCodeExporter commented 9 years ago
Thanks for the thorough explanation and code!  I've rolled in the changes so 
recording won't crash, but still need to add the .amr portion.  Will do that in 
the next couple days.

Original comment by carlhart...@gmail.com on 7 Jun 2011 at 10:57

GoogleCodeExporter commented 9 years ago

Original comment by wbrune...@gmail.com on 7 Jun 2011 at 11:39

GoogleCodeExporter commented 9 years ago
.amr has been added to 1.1.7.

Original comment by carlhart...@gmail.com on 27 Sep 2011 at 6:01