2013-04-07 8 views
5

Meine vorherige Frage (Is it possible to share an image on Android via a data URL?) bezieht sich auf diese Frage. Ich habe herausgefunden, wie ich ein Bild von meiner Anwendung an eine andere Anwendung weitergeben kann, ohne die Erlaubnis zu haben, Dateien auf externen Speicher zu schreiben. Allerdings habe ich immer noch eine Reihe von Problemverhalten:So implementieren Sie einen ContentProvider für die Bereitstellung von Bildern an Google Mail, Facebook, Evernote usw.

  1. Wenn ich versuche, das Bild von meinem Handy (Android 2.2.2) zu teilen, fatale Fehler in den Empfangs Anwendungen auftreten, und sie nicht kommen mit dem Bild überhaupt. (Könnte das ein Ergebnis eines Vorgangs in meiner App sein, der unter Android 2.2.2 nicht unterstützt wird? Oder hätte das einen Fehler in meiner App und nicht in der Ziel-App verursacht?)
  2. Wenn ich versuche, das Bild zu teilen zu Evernote, alles funktioniert gut, aber manchmal ein paar Sekunden nach dem Speichern der Notiz, bekomme ich eine Nachricht am unteren Rand der App-Bildschirm (von der Evernote App): "java.lang.SecurityException: Permission Denial: Eröffnungs-Provider com.enigmadream.picturecode.PictureContentProvider aus ProcessRecord {413db6d0 1872: com.evernote/u0a10105} (pid = 1872, uid = 10105), die nicht aus der uid 10104 exportiert wird "
  3. Wenn ich versuche, das Bild auf Facebook zu teilen, gibt es ein Rechteck für das Bild, aber kein Bild darin.

Unten ist mein ContentProvider-Code. Es muss einen einfacheren und/oder geeigneteren Weg geben, einen dateibasierten ContentProvider (insbesondere die Abfragefunktion) zu implementieren. Ich erwarte, dass viele Probleme von der Abfrage-Implementierung herrühren. Die interessante Sache ist, dass diese funktioniert sehr gut auf meinem Nexus 7, wenn Sie zu GMail gehen. Es nimmt den korrekten Anzeigenamen und die Größe für den Anhang auch auf.

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); 
    } 
... 
} 

Ich habe dies auch in der AndroidManifest.xml Datei als Geschwister der <activity> Elemente:

<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> 

Der Code, der die Absicht erstellt sieht wie folgt aus:

 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))); 

BEARBEITEN Hier sind die ersten zwei Zeilen des Stack-Trace, wenn der Fehler auf meinem Telefon auftritt:

04-07 13: 56: 24,423: E/DatabaseUtils (19431): java.lang.SecurityException: Permission Denial: Lesen com.enigmadream.picturecode.PictureContentProvider uri Gehalt: //com.enigmadream .picturecode.snapshot/picture.png? p = 01v131 & r = 36 & f = 0 a = 0 & von pid = 19025, uid = 10062 erfordert com.enigmadream.picturecode.snapshot

04-07 13 : 56: 24.423: E/DatabaseUtils (19431): um android.content.ContentProvider $ Transport.enforceReadPermission (ContentProvider .java: 271)

+0

„Wenn ich versuche, das Bild von meinem Handy (Android 2.2 zu teilen.2), fatale Fehler treten in den empfangenden Anwendungen auf, und sie kommen überhaupt nicht auf das Bild "- es sollte Stack-Traces für diese geben. Da ich noch nie ein' content: // '' Uri' verwendet habe Abfrageparameter können Sie mit dem Entfernen experimentieren. Gehen Sie stattdessen mit einer REST-artigen 'Uri'-Syntax (' content: //com.enigmadream.picturecode.snapshot/p/.../r/.../f/. ../ a /.../ picture.png'). Sie können auch die erforderlichen Berechtigungen temporär deaktivieren und nachsehen, ob das hilft, Ihr Problem zu identifizieren. " – CommonsWare

+0

Ich weiß nicht, wie ich eine Stack-Ablaufverfolgung des Fehlers bekommen könnte, wenn es so ist Ich weiß, dass die Abfrageparameter unter bestimmten Umständen funktionieren, weil ich Bilder erzeuge, die nach den Parametern generiert werden.Ist die Behandlung von Abfrageparametern in einem Inhalts-URI ein Unterschied zwischen Android-Versionen? Ich habe gerade die Bedeutung von REST unter https://en.wikipedia.org/wiki/Representational_state_transfer nachgelesen und nichts von query st gesehen klingelt (die Wortabfrage findet nicht einmal auf dieser Seite statt). Wie ist dein Beispiel eher "RESTful"? – BlueMonkMN

+0

Auch, wird es nicht ein Problem geben, wenn ich versuche, die Parameter in den Pfad einzubetten, in dem die Berechtigungen nicht mehr für alle verschiedenen Pfade gelten? – BlueMonkMN

Antwort

1

bin ich mit den Berechtigungen nicht vertraut ich bin oder erfordern bin nicht oder wie sie

ersetzen deaktivieren

Versuchen:

<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> 

mit:

<provider 
    android:name="PictureContentProvider" 
    android:authorities="com.enigmadream.picturecode.snapshot" 
    android:exported="true" 
    tools:ignore="ExportedContentProvider"> 
</provider> 

Sie sollten wirklichhabenin der ersten auch, aber die Änderung der Berechtigungen, die ich bezog sich auf die Entfernung von android:readPermission und <grant-uri-permissions>.

Dann loswerden addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); in Ihrem Java-Code.

Dadurch wird Ihre ContentProvider so eingerichtet, dass sie weltweit lesbar ist. Langfristig ist das vielleicht nicht die richtige Antwort. Kurzfristig wird es helfen, festzustellen, ob Ihr Android 2.2.2 Problem ist, weil FLAG_GRANT_READ_URI_PERMISSION nicht geehrt wird.

+0

Diese Änderung erlaubt mir, das Bild über eine GMail-Nachricht auf meinem Android 2.2-Gerät zu teilen. Ich kann mir keinen Grund vorstellen, warum diese Anwendung ein gewisses Maß an Sicherheit erfordert. Ich musste keine * irgendwelche * Berechtigungen dafür verlangen (deshalb war ich daran interessiert, das fortzusetzen), also hat es nicht viel Privileg auf dem Gerät. Es speichert keine Daten außer dem temporären PNG, das auf dem in der Benutzeroberfläche eingegebenen "Code" basiert (der wie ein Quellcode und kein Sicherheitscode ist). Irgendein Grund, es nicht so zu lassen? Facebook wird immer noch nicht zeigen, sollte ich darüber in einer separaten Frage oder ... fragen? – BlueMonkMN

+0

@BlueMonkMN: "Irgendein Grund, es nicht so zu lassen?" - Ich habe keine wirklich gute Möglichkeit, das zu beantworten. Wenn Sie mit einer beliebigen App zufrieden sind, die Ihren Provider zu jeder Zeit anspricht, gehen Sie mit. "Facebook wird immer noch nicht angezeigt" - wenn es abstürzt, unterscheidet sich die Stack-Trace wahrscheinlich von der "Permission Denial", die Sie oben zitiert haben. – CommonsWare

1

Ich hatte aufgrund der query-Methode Probleme mit der Freigabe für einige E-Mail- und Messaging-Clients. Einige Empfänger-Apps senden null für den Parameter projection. Wenn das passiert, löst Ihr Code eine NullPointerException aus. Die NPE ist einfach zu lösen. Die Apps, die null senden, benötigen jedoch noch einige Informationen zurück. Ich kann immer noch nicht mit Facebook teilen, aber ich kann mit allen anderen Apps teilen, die ich getestet habe:

EDIT Ich kann es auch nicht mit Google Hangout arbeiten. Zumindest bekomme ich einen Toast, der You can't send this file on Hangouts. Try using a picture anzeigt. Siehe auch diese Frage: Picture got deleted after send via Google hangout. Ich nehme an, dass dies darauf zurückzuführen ist, dass ich private Inhalte verwende und Hangouts dies aus irgendeinem Grund nicht akzeptieren kann/will.

@Override 
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 
     String sortOrder) { 
    if (projection == null) { 
     projection = new String[] { 
       MediaStore.MediaColumns.DISPLAY_NAME, 
       MediaStore.MediaColumns.SIZE, 
       MediaStore.MediaColumns._ID, 
       MediaStore.MediaColumns.MIME_TYPE 
     }; 
    } 

    final long time = System.currentTimeMillis(); 
    MatrixCursor result = new MatrixCursor(projection); 
    final File tempFile = generatePictureFile(uri); 

    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] = uri.getLastPathSegment(); 
     } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.SIZE) == 0) { 
      row[i] = tempFile.length(); 
     } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATA) == 0) { 
      row[i] = tempFile; 
     } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.MIME_TYPE)==0) { 
      row[i] = _mimeType; 
     } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_ADDED)==0 || 
       projection[i].compareToIgnoreCase(MediaStore.MediaColumns.DATE_MODIFIED)==0 || 
       projection[i].compareToIgnoreCase("datetaken")==0) { 
      row[i] = time; 
     } else if (projection[i].compareToIgnoreCase(MediaStore.MediaColumns._ID)==0) { 
      row[i] = 0; 
     } else if (projection[i].compareToIgnoreCase("orientation")==0) { 
      row[i] = "vertical"; 
     } 
    } 

    result.addRow(row); 
    return result; 
} 
0

hatte das gleiche Problem und nach viel Forschung und debuging hier ist mein 5 Cent: (auf meinem Android 2.3.3 und 4.2-Geräte)

  1. Facebook-App openFile(Uri uri, String mode) nicht so verwenden, werden wir muss es geben echte URI (in der Projektion [i] == MediaStore.MediaColumns.DATA) mit Leserechte.
  2. Sie haben Facebook jede Projektion geben Sie fragt, sonst FB-App

nur query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) repeate wird Es ist wahrscheinlich ein Hack, und würde auf einige sensible Daten nicht funktionieren, aber Sie können file.setReadable(true, false); machen auf interne Datei irgendwo in Ihrem Code und Facebook wird in der Lage zu sehen und senden Sie die IMG. Keine Notwendigkeit für externe Speicherrechte von Android. Wahrscheinlich wird kein Content Provider benötigt, wenn img lesbar ist. Kann nur Link in Intent senden. Aber ich hatte seltsame Google + Vorschau Bechavior ohne Anbieter so beschlossen, es zu behalten. Habe nicht viele Geräte, um nur Twitter, FB, Google+, Skype, Google Mail zu testen und zu verwenden. Adaequat jetzt

Hier ist GitHub Demo:

Verwandte Themen