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

~ use ContentProvider to support sharing log file on Android API >= 24 #5

Merged
merged 2 commits into from
May 3, 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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ The lib uses native java logging and is <b>very small</b>.
Usage
-----

Add the content provider to your application manifest

<pre>
&lt;provider
android:name="at.pansy.android.logging.helper.LogFileProvider"
android:authorities="${applicationId}.logfileprovider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true" /&gt;
</pre>

Initialize logging in your application class

<pre>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.android.tools.build:gradle:2.3.0'
}
}

Expand Down
12 changes: 6 additions & 6 deletions demo/build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
compileSdkVersion 25
buildToolsVersion "25.0.2"

defaultConfig {
minSdkVersion 8
targetSdkVersion 23
minSdkVersion 9
targetSdkVersion 25
versionCode 1
versionName "1.2"
versionName "1.3"
}
buildTypes {
release {
Expand All @@ -20,6 +20,6 @@ android {

dependencies {
compile project(':lib')
compile 'com.android.support:appcompat-v7:23.+'
compile 'com.android.support:appcompat-v7:25.+'
compile fileTree(dir: 'libs', include: ['*.jar'])
}
9 changes: 7 additions & 2 deletions demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.pansy.android.logging.helper.demo" >

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
android:name=".DemoApplication"
android:allowBackup="true"
Expand All @@ -18,6 +16,13 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<provider
android:name="at.pansy.android.logging.helper.LogFileProvider"
android:authorities="${applicationId}.logfileprovider"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true" />
</application>

</manifest>
8 changes: 4 additions & 4 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ apply plugin: 'maven'

group = 'at.pansy.android'
archivesBaseName = 'android-logging-helper'
version = '1.2.1'
version = '1.3.0'

android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
compileSdkVersion 25
buildToolsVersion "25.0.2"

defaultConfig {
minSdkVersion 8
minSdkVersion 9
targetSdkVersion 23
versionCode 1
versionName version
Expand Down
153 changes: 153 additions & 0 deletions lib/src/main/java/at/pansy/android/logging/helper/LogFileProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package at.pansy.android.logging.helper;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.OpenableColumns;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LogFileProvider extends ContentProvider {

private static final Logger logger = Logger.getLogger(LogFileProvider.class.getName());

/* package */ static final String EXPECTED_URI_PATH = "/logfile/";

/* package */ static final String DESTINATION_FILENAME = "logfile.log.gz";

private static final String[] COLUMNS = {
OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE,
};

public LogFileProvider() {
}

public static Uri createFileUri(Context context, String tag) {
return new Uri.Builder()
.scheme("content")
.authority(context.getPackageName() + ".logfileprovider")
.path(EXPECTED_URI_PATH + tag)
.build();
}

public static File getDestinationFile(Context context) {
return new File(context.getCacheDir(), DESTINATION_FILENAME);
}

@Override
public boolean onCreate() {
return true;
}

private boolean isValidPath(Uri uri) {
return uri.getEncodedPath() != null && uri.getEncodedPath().startsWith(EXPECTED_URI_PATH);
}

@Override
public String getType(Uri uri) {
if (isValidPath(uri)) {
return "application/x-gzip";
}

return null;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (!isValidPath(uri)) {
throw new UnsupportedOperationException("file is unavailable");
}

String tag = "logfile";
List<String> segments = uri.getPathSegments();
if (segments.size() >= 2) {
tag = segments.get(1);
}

if (projection == null) {
projection = COLUMNS;
}

String[] columns = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;

for (String column : projection) {
Object value = null;

if (OpenableColumns.DISPLAY_NAME.equals(column)) {
value = tag + ".log.gz";
} else if (OpenableColumns.SIZE.equals(column)) {
try {
File file = getDestinationFile(getContext());
value = file.length();
} catch (Exception e) {
logger.log(Level.SEVERE, "failed to read file length", e);
value = 0;
}
}
// support for obscure apps, see: https://github.com/commonsguy/cwac-provider#supporting-legacy-apps
else if (MediaStore.MediaColumns.MIME_TYPE.equals(column)) {
value = getType(uri);
} else if (MediaStore.MediaColumns.DATA.equals(column)) {
value = uri.toString();
}

if (value != null) {
columns[i] = column;
values[i] = value;
}

i++;
}

columns = Arrays.copyOf(columns, i);
values = Arrays.copyOf(values, i);

MatrixCursor cursor = new MatrixCursor(columns, 1);
cursor.addRow(values);

return cursor;
}

@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (!isValidPath(uri)) {
throw new UnsupportedOperationException("file is unavailable");
}

File file = getDestinationFile(getContext());
if (!file.exists()) {
throw new FileNotFoundException();
}

return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}

@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("content provider is read-only");
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("content provider is read-only");
}

@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
throw new UnsupportedOperationException("content provider is read-only");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;

import java.io.BufferedReader;
Expand Down Expand Up @@ -75,17 +74,17 @@ public static void shareLog(final Context context, final String email, final Str

private static void doShareLog(final Context context, final String tag, final String email, final String subject, final String body) {

new AsyncTask<Void, Void, Uri>() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Uri doInBackground(Void... params) {
protected Boolean doInBackground(Void... params) {
BufferedReader reader;
BufferedWriter writer;

try {
File cacheDir = context.getCacheDir();
if (cacheDir != null) {

File zipFile = new File(context.getExternalCacheDir(), tag + ".log.gz");
File zipFile = LogFileProvider.getDestinationFile(context);
writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(zipFile))));

ArrayList<File> logFiles = new ArrayList<>();
Expand All @@ -107,17 +106,17 @@ protected Uri doInBackground(Void... params) {

writer.close();

return Uri.fromFile(zipFile);
return true;
}
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
return null;
return false;
}

@Override
protected void onPostExecute(Uri result) {
if (result != null) {
protected void onPostExecute(Boolean success) {
if (success != null && success) {
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
intent.setType("application/x-gzip");
if (email != null) {
Expand All @@ -131,7 +130,8 @@ protected void onPostExecute(Uri result) {
// either have EXTRA_TEXT or EXTRA_STREAM set, both setting both seems to be respected by most receivers (e.g. GMail)
intent.putExtra(android.content.Intent.EXTRA_TEXT, body);
}
intent.putExtra(android.content.Intent.EXTRA_STREAM, result);
intent.putExtra(android.content.Intent.EXTRA_STREAM, LogFileProvider.createFileUri(context, tag));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

try {
context.startActivity(intent);
Expand Down