Android ContentProvider openFile: need to serve “modified” file

偶尔善良 提交于 2020-02-27 12:27:22

问题


I want to serve image files that saved on "external storage" via ContentProvider.

These image files are 'mangled' - first 50 bytes are XORed with some arbitary value. I want to do 'demangle' within ContentProvider so that other applications don't need to do special treatment.

I'm using Mininum SDK version 14.

Here is my first try - using piped ParcelFileDescriptor:

public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    // basic uri/mode check here
    return openPipeHelper(uri, getType(uri), null, new FileInputStream(getImageFile(uri)), new PipeDataWriter<InputStream>() {
        @Override
        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, final String mimeType, Bundle opts, InputStream input) {
            InputStream fin = new FilterInputStream(input) {
                private int cnt = 0;
                private byte mask;

                @Override
                public int read() throws IOException {
                    byte[] buffer = new byte[1];
                    return read(buffer) == -1 ? -1 : (buffer[0] & 0xff);
                }

                @Override
                public int read(@NonNull byte[] buffer) throws IOException {
                    return read(buffer, 0, buffer.length);
                }

                @Override
                public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
                    int ret = super.read(buffer, byteOffset, byteCount);
                    if (ret <= 0) return ret;
                    if (cnt == 0) {
                        switch (mimeType) {
                            case "image/png":
                                mask = (byte) (buffer[byteOffset] ^ 137);
                                break;
                            case "image/webp":
                                mask = (byte) (buffer[byteOffset] ^ 'R');
                                break;
                        }
                    }
                    for (int i = byteOffset; i < byteOffset + ret && cnt < 50; i++, cnt++) {
                        buffer[i] ^= mask;
                    }
                    return ret;
                }
            };
            OutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(output);
            byte[] buf = new byte[1024 * 1024];
            try {
                while (true) {
                    int n = fin.read(buf);
                    if (n == -1) break;
                    Log.i(TAG, "openFile get n=" + n);
                    fout.write(buf, 0, n);
                    fout.flush();
                }
            } catch (IOException ex) {
                // EPIPE likely means pipe closed on other end; treat it as WAI.
                if (!ex.getMessage().contains("EPIPE")) {
                    Log.w(TAG, "openFile failed", ex);
                }
            } finally {
                try {
                    fin.close();
                } catch (IOException ex) {
                    Log.w(TAG, "openFile failed closing input", ex);
                }
                try {
                    fout.close();
                } catch (IOException ex) {
                    Log.w(TAG, "openFile failed closing output", ex);
                }
            }
        }
    });
}

Result:

  • Works well with ImageView.setImageURI().
  • Don't work with android default Gallery (Intent.ACTION_VIEW with setDataAndType())
  • Works well with ES image viewer

It seems that Gallery don't like "piped stream".

Here is second try - read whole file, demangle, and serve as ParcelFileDescriptor.fromData():

    File file = getImageFile(uri);
    byte[] buffer = readFully(file);
    String mimeType = getType(uri);
    byte mask;
    switch (mimeType) {
        case "image/png":
            mask = (byte) (buffer[0] ^ 137);
            break;
        case "image/webp":
            mask = (byte) (buffer[0] ^ 'R');
            break;
        default:
            mask = 0;
            break;
    }
    for (int i = 0; i < 50; i++) buffer[i] ^= mask;
    return (ParcelFileDescriptor) ParcelFileDescriptor.class.getMethod("fromData", byte[].class, String.class).invoke(null, buffer, getImageFile(uri).getName());

Result:

  • Don't work well with ImageView.setImageURI().
  • Works well with android default Gallery
  • Works well with ES image viewer

It seems that from time to time, MemoryFile made in ParcelFileDescriptor.fromData() is closed and disposed before ImageView.setImageURI() get data.

Here is third try - write demangled image to temporary file:

    // buffer contains readFully and demangled image binary
    try {
        File tmpFile = File.createTempFile("image", getImageExtension(uri));
        OutputStream os = new FileOutputStream(tmpFile);
        try {
            os.write(buffer);
        } finally {
            try {
                os.close();
            } catch (IOException ex2) {
                Log.w(TAG, "openFile(): closing failed", ex2);
            }
        }
        return ParcelFileDescriptor.open(tmpFile, ParcelFileDescriptor.MODE_READ_ONLY);
    } catch (IOException ex2) {
        Log.e(TAG, "openFile(): writing failed", ex2);
        return null;
    }

Result:

  • Works well with ImageView.setImageURI().
  • Works well with android default Gallery
  • Works well with ES image viewer

However, I don't like this solution, as it is very hard to determine when I could delete temporary files.

These 3 solutions have their flaws, and I couldn't find flawless solution. What is the "right" way to do such things?

来源:https://stackoverflow.com/questions/27591756/android-contentprovider-openfile-need-to-serve-modified-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!