Merge pull request #986 from UnknownShadow200/AndroidContentProvider2

Add proper android content:// provider
This commit is contained in:
UnknownShadow200 2022-11-20 15:27:26 +11:00 committed by GitHub
commit 04e5993bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 138 additions and 21 deletions

View File

@ -11,6 +11,12 @@
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="26"/> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="26"/>
<application android:icon="@mipmap/ccicon" android:label="ClassiCube"> <application android:icon="@mipmap/ccicon" android:label="ClassiCube">
<provider
android:name="com.classicube.CCFileProvider"
android:authorities="com.classicube.android.client.provider"
android:exported="false"
android:grantUriPermissions="true" >
</provider>
<activity android:name="com.classicube.MainActivity" android:label="ClassiCube" <activity android:name="com.classicube.MainActivity" android:label="ClassiCube"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden"> android:configChanges="orientation|screenSize|keyboard|keyboardHidden">

View File

@ -0,0 +1,115 @@
package com.classicube;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
public class CCFileProvider extends ContentProvider
{
final static String[] DEFAULT_COLUMNS = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, MediaStore.MediaColumns.DATA };
File root;
@Override
public boolean onCreate() {
return true;
}
@Override
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
root = context.getExternalFilesDir(null); // getGameDataDirectory
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
File file = getFileForUri(uri);
// can be null when caller is requesting all columns
if (projection == null) projection = DEFAULT_COLUMNS;
ArrayList<String> cols = new ArrayList<String>(3);
ArrayList<Object> vals = new ArrayList<Object>(3);
for (String column : projection) {
if (column.equals(OpenableColumns.DISPLAY_NAME)) {
cols.add(OpenableColumns.DISPLAY_NAME);
vals.add(file.getName());
} else if (column.equals(OpenableColumns.SIZE)) {
cols.add(OpenableColumns.SIZE);
vals.add(file.length());
} else if (column.equals(MediaStore.MediaColumns.DATA)) {
cols.add(MediaStore.MediaColumns.DATA);
vals.add(file.getAbsolutePath());
}
}
// https://stackoverflow.com/questions/4042434/converting-arrayliststring-to-string-in-java
MatrixCursor cursor = new MatrixCursor(cols.toArray(new String[0]), 1);
cursor.addRow(vals.toArray());
return cursor;
}
@Override
public String getType(Uri uri) {
String path = uri.getEncodedPath();
int sepExt = path.lastIndexOf('.');
if (sepExt >= 0) {
String fileExt = path.substring(sepExt);
if (fileExt.equals(".png")) return "image/png";
}
return "application/octet-stream";
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Readonly access");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Readonly access");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Readonly access");
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file = getFileForUri(uri);
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
}
public static Uri getUriForFile(String path) {
// See AndroidManifest.xml for authority
return new Uri.Builder()
.scheme("content")
.authority("com.classicube.android.client.provider")
.encodedPath(Uri.encode(path, "/"))
.build();
}
File getFileForUri(Uri uri) {
String path = uri.getPath();
File file = new File(root, path);
file = file.getAbsoluteFile();
// security validation check
if (!file.getPath().startsWith(root.getPath())) {
throw new SecurityException("Resolved path lies outside app directory:" + path);
}
return file;
}
}

View File

@ -26,7 +26,6 @@ import android.database.Cursor;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.StrictMode;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.text.Editable; import android.text.Editable;
@ -159,19 +158,6 @@ public class MainActivity extends Activity
runGameAsync(); runGameAsync();
} }
void HACK_avoidFileUriExposedErrors() {
// StrictMode - API level 9
// disableDeathOnFileUriExposure - API level 24 ?????
try {
Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
m.invoke(null);
} catch (NoClassDefFoundError ex) {
ex.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
// requestWindowFeature - API level 1 // requestWindowFeature - API level 1
@ -193,9 +179,6 @@ public class MainActivity extends Activity
// renderOverDisplayCutouts(); // renderOverDisplayCutouts();
// TODO: semaphore for destroyed and surfaceDestroyed // TODO: semaphore for destroyed and surfaceDestroyed
// avoid FileUriExposed exception when taking screenshots on recent Android versions
HACK_avoidFileUriExposedErrors();
if (!gameRunning) startGameAsync(); if (!gameRunning) startGameAsync();
// TODO rethink to avoid this // TODO rethink to avoid this
if (gameRunning) updateInstance(); if (gameRunning) updateInstance();
@ -811,11 +794,21 @@ public class MainActivity extends Activity
public String shareScreenshot(String path) { public String shareScreenshot(String path) {
try { try {
File file = new File(getGameDataDirectory() + "/screenshots/" + path); Uri uri;
if (android.os.Build.VERSION.SDK_INT >= 23){ // android 6.0
uri = CCFileProvider.getUriForFile("screenshots/" + path);
} else {
// when trying to use content:// URIs on my android 4.0.3 test device
// - 1 app crashed
// - 1 app wouldn't show image previews
// so fallback to file:// on older devices as they seem to reliably work
File file = new File(getGameDataDirectory() + "/screenshots/" + path);
uri = Uri.fromFile(file);
}
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND); intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png"); intent.setType("image/png");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(intent, "share via")); startActivity(Intent.createChooser(intent, "share via"));

View File

@ -179,9 +179,12 @@ build_android() {
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/Makefile # https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/Makefile
# https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/CMakeLists.txt # https://github.com/skanti/Android-Manual-Build-Command-Line/blob/master/hello-jni/CMakeLists.txt
# compile interop java file into its multiple .class files # compile java files into multiple .class files
javac java/com/classicube/MainActivity.java -d ./obj -classpath $SDK_ROOT/android.jar cd $ROOT_DIR/android/app/src/main/java/com/classicube
javac *.java -d $ROOT_DIR/android/app/src/main/obj -classpath $SDK_ROOT/android.jar
if [ $? -ne 0 ]; then echo "Failed to compile Android Java" >> "$ERRS_FILE"; fi if [ $? -ne 0 ]; then echo "Failed to compile Android Java" >> "$ERRS_FILE"; fi
cd $ROOT_DIR/android/app/src/main
# compile the multiple .class files into one .dex file # compile the multiple .class files into one .dex file
$TOOLS_ROOT/dx --dex --output=obj/classes.dex ./obj $TOOLS_ROOT/dx --dex --output=obj/classes.dex ./obj
# create initial .apk with packaged version of resources # create initial .apk with packaged version of resources