My previous question ( Is it possible to share an image on Android via the data URL? ) Is related to this question. I figured out how to split an image from my application to another application without permission to write files to external storage. However, I still get a number of problematic behaviors:
- When I try to transfer an image from my phone (Android 2.2.2), fatal errors occur in the receiving applications and they do not display the image at all. (Could this be the result of some operation in my application that is not supported on Android 2.2.2? Or did it cause an error in my application and not in the target application?)
- When I try to share the image with Evernote, everything works fine, but sometimes a few seconds after saving the note, I get a message at the bottom of my application screen (from the Evernote App): "java.lang.SecurityException: Permission Denial: opening the com.enigmadream provider. picturecode.PictureContentProvider from ProcessRecord {413db6d0 1872: com.evernote / u0a10105} (pid = 1872, uid = 10105), which is not exported from uid 10104 "
- When I try to share a photo with Facebook, there is a rectangle for the image, but there is no image in it.
Below is the code of my ContentProvider. There should be a simpler and / or more correct way to implement file-based ContentProvider (especially the request function). I expect that many problems will arise from the implementation of the request. Interestingly, this works very well on my Nexus 7 when switching to GMail. It also displays the correct display name and size for the attachment.
public class PictureContentProvider extends ContentProvider implements AutoAnimate {
public static final Uri CONTENT_URI = Uri.parse("content://com.enigmadream.picturecode.snapshot/picture.png");
private static String[] mimeTypes = {"image/png"};
private Uri generatedUri;
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new RuntimeException("PictureContentProvider.delete not supported");
}
@Override
public String getType(Uri uri) {
return "image/png";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new RuntimeException("PictureContentProvider.insert not supported");
}
@Override
public boolean onCreate() {
generatedUri = Uri.EMPTY;
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
long fileSize = 0;
MatrixCursor result = new MatrixCursor(projection);
File tempFile;
try {
tempFile = generatePictureFile(uri);
fileSize = tempFile.length();
} catch (FileNotFoundException ex) {
return result;
}
Object[] row = new Object[projection.length];
for (int i = 0; i < projection.length; i++) {
if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DISPLAY_NAME) == 0) {
row[i] = getContext().getString(R.string.snapshot_displaystring);
} else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) {
row[i] = fileSize;
} else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) {
row[i] = tempFile;
} else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) {
row[i] = "image/png";
}
}
result.addRow(row);
return result;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new RuntimeException("PictureContentProvider.update not supported");
}
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
return mimeTypes;
}
private File generatePictureFile(Uri uri) throws FileNotFoundException {
if (generatedUri.compareTo(uri)==0)
return new File(getContext().getFilesDir(), "picture.png");;
Context context = getContext();
String query = uri.getQuery();
String[] queryParts = query.split("&");
String pictureCode = "016OA";
int resolution = 36;
int frame = 0;
int padding = 0;
for (String param : queryParts) {
if (param.length() < 2)
continue;
if (param.substring(0,2).compareToIgnoreCase("p=") == 0) {
pictureCode = param.substring(2);
} else if (param.substring(0,2).compareToIgnoreCase("r=") == 0) {
resolution = Integer.parseInt(param.substring(2));
} else if (param.substring(0, 2).compareToIgnoreCase("f=") == 0) {
frame = Integer.parseInt(param.substring(2));
} else if (param.substring(0, 2).compareToIgnoreCase("a=") == 0) {
padding = Integer.parseInt(param.substring(2));
}
}
Bitmap picture = RenderPictureCode(pictureCode, resolution, frame, padding);
File tempFile = new File(context.getFilesDir(), "picture.png");
FileOutputStream stream;
stream = new FileOutputStream(tempFile);
picture.compress(CompressFormat.PNG, 90, stream);
try {
stream.flush();
stream.close();
} catch (IOException e) {
e.printStackTrace();
throw new Error(e);
}
picture.recycle();
generatedUri = uri;
return tempFile;
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File tempFile = generatePictureFile(uri);
return ParcelFileDescriptor.open(tempFile, ParcelFileDescriptor.MODE_READ_ONLY);
}
...
}
I also have this in the AndroidManifest.xml file as a native element <activity>:
<provider
android:name="PictureContentProvider"
android:authorities="com.enigmadream.picturecode.snapshot"
android:grantUriPermissions="true"
android:readPermission="com.enigmadream.picturecode.snapshot"
tools:ignore="ExportedContentProvider">
<grant-uri-permission android:path="/picture.png" />
</provider>
The code that creates the intent is as follows:
resolution = mPicView.getWidth();
if (mPicView.getHeight() > resolution)
resolution = mPicView.getHeight();
String paddingText = mPadding.getEditableText().toString();
int padding;
try {
padding = Integer.parseInt(paddingText);
} catch (NumberFormatException ex) {
padding = 0;
}
Uri uri = Uri.parse(PictureContentProvider.CONTENT_URI
+ "?p=" + Uri.encode(mPicView.getPictureCode()) + "&r=" + Integer.toString(resolution)
+ "&f=" + Integer.toString(mPicView.getFrame()) + "&a=" + Integer.toString(padding));
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("image/png");
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_subject_made));
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(share, getString(R.id.menu_share)));
EDIT
Here are the first two lines of the stack trace when an error occurs on my phone:
04-07 13: 56: 24.423: E/DatabaseUtils (19431): java.lang.SecurityException: : com.enigmadream.picturecode.PictureContentProvider uri ://com.enigmadream.picturecode.snapshot/picture.png = 01v131 & = 36 & F = 0 & = 0 pid = 19025, uid = 10062 com.enigmadream.picturecode.snapshot
04-07 13: 56: 24.423: E/DatabaseUtils (19431): android.content.ContentProvider $Transport.enforceReadPermission(ContentProvider.java:271)