Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File attachment support (fix) #565

Merged
merged 2 commits into from
Mar 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion acra/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
android:process=":acra" />

<provider
android:authorities="org.acra.provider"
android:authorities="${applicationId}.acra"
android:name="org.acra.attachment.AcraContentProvider"
android:exported="false"
android:process=":acra"
Expand Down
5 changes: 3 additions & 2 deletions acra/src/main/java/org/acra/annotation/ReportsCrashes.java
Original file line number Diff line number Diff line change
Expand Up @@ -634,8 +634,9 @@
* Allows to attach files to crash reports.
* <p>
* ACRA contains a file provider under the following Uri:
* content://org.acra.provider/[Directory]/[Path]
* where [Directory] is one of the enum constants in {@link Directory} in lower case
* content://[applicationId].acra/[Directory]/[Path]
* where [applicationId] is your application package name,
* [Directory] is one of the enum constants in {@link Directory} in lower case
* and [Path] is the relative path to the file in that directory
* e.g. content://org.acra.provider/files/thisIsATest.txt
* </p>
Expand Down
45 changes: 26 additions & 19 deletions acra/src/main/java/org/acra/attachment/AcraContentProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import org.acra.ACRA;
import org.acra.file.Directory;
import org.acra.http.HttpUtils;

Expand All @@ -45,38 +47,36 @@

public class AcraContentProvider extends ContentProvider {
private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE };
private static final String AUTHORITY = "org.acra.provider";
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
private static final String ANY_MATCH = "/*";
private final UriMatcher uriMatcher;

public AcraContentProvider() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
for (Directory directory : Directory.values()){
uriMatcher.addURI(AUTHORITY, directory.name().toLowerCase() + ANY_MATCH, directory.ordinal());
}
}
private UriMatcher uriMatcher;

@Override
public boolean onCreate() {
final String authority = getContext().getPackageName() + ".acra";
if (ACRA.DEV_LOGGING) ACRA.log.d(ACRA.LOG_TAG, "Registered content provider for authority " + authority);
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
for (Directory directory : Directory.values()) {
uriMatcher.addURI(authority, directory.name().toLowerCase() + ANY_MATCH, directory.ordinal());
}
return true;
}

@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
final File file = getFileForUri(uri);
if(file == null){
if (file == null) {
return null;
}
if(projection == null){
if (projection == null) {
projection = COLUMNS;
}
final Map<String, Object> columnValueMap = new LinkedHashMap<String, Object>();
for (String column : projection){
for (String column : projection) {
if (column.equals(OpenableColumns.DISPLAY_NAME)) {
columnValueMap.put(OpenableColumns.DISPLAY_NAME, file.getName());
}else if(column.equals(OpenableColumns.SIZE)){
} else if (column.equals(OpenableColumns.SIZE)) {
columnValueMap.put(OpenableColumns.SIZE, file.length());
}
}
Expand All @@ -86,18 +86,18 @@ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable S
}

@Nullable
private File getFileForUri(Uri uri){
private File getFileForUri(Uri uri) {
final int match = uriMatcher.match(uri);
if(match == UriMatcher.NO_MATCH) {
if (match == UriMatcher.NO_MATCH) {
return null;
}
final List<String> segments = new ArrayList<String>(uri.getPathSegments());
if(segments.size() < 2) return null;
if (segments.size() < 2) return null;
final String dir = segments.remove(0).toUpperCase();
try {
final Directory directory = Directory.valueOf(dir);
return directory.getFile(getContext(), TextUtils.join(File.separator, segments));
}catch (IllegalArgumentException e){
} catch (IllegalArgumentException e) {
return null;
}
}
Expand Down Expand Up @@ -128,7 +128,14 @@ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable St
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
final File file = getFileForUri(uri);
if(file == null || ! file.exists()) throw new FileNotFoundException("File represented by uri "+uri+ " could not be found");
if (file == null || !file.exists()) throw new FileNotFoundException("File represented by uri " + uri + " could not be found");
if(ACRA.DEV_LOGGING) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ACRA.log.d(ACRA.LOG_TAG, getCallingPackage() + " opened " + file.getPath());
}else {
ACRA.log.d(ACRA.LOG_TAG, file.getPath() + " was opened by an application");
}
}
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
}
19 changes: 10 additions & 9 deletions acra/src/main/java/org/acra/file/Directory.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import android.content.Context;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;

import java.io.File;
Expand All @@ -33,7 +34,7 @@ public enum Directory {
*/
FILES_LEGACY {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return (fileName.startsWith("/") ? Directory.ROOT : Directory.FILES).getFile(context, fileName);
}
},
Expand All @@ -42,7 +43,7 @@ public File getFile(Context context, String fileName) {
*/
FILES {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(context.getFilesDir(), fileName);
}
},
Expand All @@ -51,7 +52,7 @@ public File getFile(Context context, String fileName) {
*/
EXTERNAL_FILES {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(context.getExternalFilesDir(null), fileName);
}
},
Expand All @@ -60,7 +61,7 @@ public File getFile(Context context, String fileName) {
*/
CACHE {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(context.getCacheDir(), fileName);
}
},
Expand All @@ -69,7 +70,7 @@ public File getFile(Context context, String fileName) {
*/
EXTERNAL_CACHE {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(context.getExternalCacheDir(), fileName);
}
},
Expand All @@ -79,7 +80,7 @@ public File getFile(Context context, String fileName) {
*/
NO_BACKUP_FILES {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(ContextCompat.getNoBackupFilesDir(context), fileName);
}
},
Expand All @@ -88,7 +89,7 @@ public File getFile(Context context, String fileName) {
*/
EXTERNAL_STORAGE {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File(Environment.getExternalStorageDirectory(), fileName);
}
},
Expand All @@ -97,10 +98,10 @@ public File getFile(Context context, String fileName) {
*/
ROOT {
@Override
public File getFile(Context context, String fileName) {
public File getFile(@NonNull Context context, @NonNull String fileName) {
return new File("/", fileName);
}
};

public abstract File getFile(Context context, String fileName);
public abstract File getFile(@NonNull Context context, @NonNull String fileName);
}
7 changes: 4 additions & 3 deletions acra/src/main/java/org/acra/sender/EmailIntentSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void send(@NonNull Context context, @NonNull CrashReportData errorContent
context.startActivity(resolveIntent);
} else {
String packageName = componentName.getPackageName();
final Intent emailIntent = new Intent(attachments.size() == 1 ? Intent.ACTION_SEND : Intent.ACTION_SEND_MULTIPLE);
final Intent emailIntent = new Intent(Intent.ACTION_SEND_MULTIPLE);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{config.mailTo()});
emailIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
Expand All @@ -86,7 +86,7 @@ public void send(@NonNull Context context, @NonNull CrashReportData errorContent
emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
if (packageName.equals("android")) {
//multiple activities support the intent and no default is set
final List<Intent> initialIntents = buildInitialIntents(pm, resolveIntent, emailIntent);
final List<Intent> initialIntents = buildInitialIntents(context, pm, resolveIntent, emailIntent, attachments);
if (initialIntents.size() > 1) {
showChooser(context, initialIntents);
return;
Expand All @@ -109,12 +109,13 @@ public void send(@NonNull Context context, @NonNull CrashReportData errorContent
}
}

private List<Intent> buildInitialIntents(@NonNull PackageManager pm, @NonNull Intent resolveIntent, @NonNull Intent emailIntent) {
private List<Intent> buildInitialIntents(@NonNull Context context, @NonNull PackageManager pm, @NonNull Intent resolveIntent, @NonNull Intent emailIntent, @NonNull List<Uri> attachments) {
final List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(resolveIntent, PackageManager.MATCH_DEFAULT_ONLY);
final List<Intent> initialIntents = new ArrayList<Intent>();
for (ResolveInfo info : resolveInfoList) {
final Intent packageSpecificIntent = new Intent(emailIntent);
packageSpecificIntent.setPackage(info.activityInfo.packageName);
grantPermission(context, emailIntent, info.activityInfo.packageName, attachments);
if (packageSpecificIntent.resolveActivity(pm) != null) {
initialIntents.add(packageSpecificIntent);
}
Expand Down