Ma question précédente ( Est-il possible de partager une image sur Android via une URL de données ? ) est lié à cette question. J'ai compris comment partager une image de mon application vers une autre application sans avoir la permission d'écrire des fichiers sur le stockage externe. Cependant, j'obtiens toujours un certain nombre de comportements problématiques :
- Lorsque j'essaie de partager l'image à partir de mon téléphone (Android 2.2.2), des erreurs fatales se produisent dans les applications réceptrices, et l'image ne s'affiche pas du tout. (Cela pourrait-il être le résultat d'une opération dans mon application qui n'est pas prise en charge par Android 2.2.2 ? Ou cela aurait-il provoqué une erreur dans mon application plutôt que dans l'application cible) ?
- Quand j'essaie de partager l'image avec Evernote, tout fonctionne bien, mais parfois quelques secondes après l'enregistrement de la note, je reçois un message en bas de l'écran de mon application (de l'application Evernote) : "java.lang.SecurityException : Déni de permission : ouverture du fournisseur com.enigmadream.picturecode.PictureContentProvider à partir du ProcessRecord{413db6d0 1872:com.evernote/u0a10105} (pid=1872, uid=10105) qui n'est pas exporté à partir de l'uid 10104".
- Lorsque j'essaie de partager l'image sur Facebook, il y a un rectangle pour l'image, mais aucune image dedans.
Voici mon code ContentProvider. Il doit y avoir un moyen plus facile et/ou plus approprié d'implémenter un ContentProvider basé sur des fichiers (en particulier la fonction de requête). Je pense que la plupart des problèmes proviennent de l'implémentation de la requête. Ce qui est intéressant, c'est que ce fait fonctionnent très bien sur mon Nexus 7 lorsqu'on va sur GMail. Le nom d'affichage et la taille de la pièce jointe sont également pris en compte.
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);
}
...
}
Je l'ai également placé dans le fichier AndroidManifest.xml en tant que membre de la famille du fichier <activity>
éléments :
<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>
Le code qui crée l'intention ressemble à ceci :
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 Voici les deux premières lignes de la trace de la pile lorsque l'erreur se produit sur mon téléphone :
04-07 13:56:24.423 : E/DatabaseUtils(19431) : java.lang.SecurityException : Déni de permission : lecture uri de com.enigmadream.picturecode.PictureContentProvider content://com.enigmadream.picturecode.snapshot/picture.png?p=01v131&r=36&f=0&a=0 à partir de pid=19025, uid=10062 exige com.enigmadream.picturecode.snapshot
04-07 13:56:24.423 : E/DatabaseUtils(19431) : at Android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:271)