Try to implement write permission checking and requesting permissions on newer Android devices

This commit is contained in:
Alibek Omarov (a1batross) 2017-03-07 00:00:16 +03:00
parent 139972e5c8
commit ea60581142
4 changed files with 251 additions and 163 deletions

View file

@ -61,12 +61,12 @@
<string name="write_failed">Write test has failed</string>
<string name="ask_about_new_basedir_msg">Move your game files somewhere else, for example Android/data/%s or internal memory. At next run I will ask you about folder again.</string>
<string name="ask_about_new_basedir_msg">Move your game files somewhere else, for example Android/data/in.celest.xash3d.hl or internal memory. At next run I will ask you about folder again.</string>
<string name="lollipop_request_permission_msg">Due to writing politics of newer Android versions, you need to select a root folder of storage where game data is located.</string>
<string name="lollipop_select_folder_btn">Select folder</string>
<!-- These strings must be concat with a "ask_about_new_basedir" string -->
<string name="lollipop_request_permission_fail_msg">Write test has been failed twice. </string>
<string name="kitkat_write_fail_msg">Due to writing politics of Android 4.4, you can't use this storage. </string>
<string name="kitkat_write_fail_msg">Due to writing politics of Android 4.4, you can\'t use this storage. </string>
<string name="readonly_fs_fail_msg">Seems you have read-only filesystem. </string>
</resources>

View file

@ -0,0 +1,72 @@
package in.celest.xash3d;
import android.content.*;
import android.view.*;
import android.os.*;
import android.util.*;
import android.graphics.*;
import android.text.method.*;
import android.text.*;
import android.media.*;
import android.hardware.*;
import android.content.*;
import android.widget.*;
import android.content.pm.*;
import java.lang.*;
import java.util.List;
import java.security.MessageDigest;
import in.celest.xash3d.hl.BuildConfig;
import in.celest.xash3d.XashConfig;
public class CertCheck
{
// Certificate checking
private static String SIG = "DMsE8f5hlR7211D8uehbFpbA0n8=";
private static String SIG_TEST = ""; // a1ba: mittorn, add your signature later
private static String TAG = "XASH3D:CertCheck";
public static boolean dumbAntiPDALifeCheck( Context context )
{
if( BuildConfig.DEBUG ||
!XashConfig.CHECK_SIGNATURES )
return false; // disable checking for debug builds
try
{
PackageInfo info = context.getPackageManager()
.getPackageInfo( context.getPackageName(), PackageManager.GET_SIGNATURES );
for( Signature signature: info.signatures )
{
MessageDigest md = MessageDigest.getInstance( "SHA" );
final byte[] signatureBytes = signature.toByteArray();
md.update( signatureBytes );
final String curSIG = Base64.encodeToString( md.digest(), Base64.NO_WRAP );
if( XashConfig.PKG_TEST )
{
if( SIG_TEST.equals(curSIG) )
return false;
}
else
{
if( SIG.equals(curSIG) )
return false;
}
}
}
catch( Exception e )
{
e.printStackTrace();
}
Log.e(TAG, "Please, don't resign our public release builds!");
Log.e(TAG, "If you want to insert some features, rebuild package with ANOTHER package name from git repository.");
return true;
}
}

View file

@ -55,6 +55,7 @@ import org.json.*;
import in.celest.xash3d.hl.R;
import in.celest.xash3d.XashActivity;
import in.celest.xash3d.CertCheck;
public class LauncherActivity extends Activity {
// public final static String ARGV = "in.celest.xash3d.MESSAGE";
@ -89,7 +90,7 @@ public class LauncherActivity extends Activity {
super.setTheme( 0x01030224 );
else super.setTheme( 0x01030005 );
if( XashActivity.dumbAntiPDALifeCheck( this ) )
if( CertCheck.dumbAntiPDALifeCheck( this ) )
{
finish();
return;

View file

@ -19,6 +19,9 @@ import android.hardware.*;
import android.content.*;
import android.widget.*;
import android.content.pm.*;
import android.net.Uri;
import android.provider.*;
import android.database.*;
import android.view.inputmethod.*;
@ -26,9 +29,11 @@ import java.lang.*;
import java.util.List;
import java.security.MessageDigest;
import in.celest.xash3d.hl.R;
import in.celest.xash3d.hl.BuildConfig;
import in.celest.xash3d.XashConfig;
import in.celest.xash3d.JoystickHandler;
import in.celest.xash3d.CertCheck;
/**
Xash Activity
@ -47,13 +52,16 @@ public class XashActivity extends Activity {
public static JoystickHandler handler;
public static ImmersiveMode mImmersiveMode;
public static boolean keyboardVisible = false;
public static boolean mEngineReady = false;
private static Vibrator mVibrator;
private static boolean mHasVibrator;
private int mReturingWithResultCode = 0;
private static int OPEN_DOCUMENT_TREE_RESULT = 1;
private static int FPICKER_RESULT = 2;
// Joystick constants
public final static byte JOY_HAT_CENTERED = 0; // bitmasks for hat current status
public final static byte JOY_HAT_UP = 1;
@ -73,9 +81,6 @@ public class XashActivity extends Activity {
private static boolean mUseVolume;
public static View mDecorView;
// Certificate checking
private static String SIG = "DMsE8f5hlR7211D8uehbFpbA0n8=";
private static String SIG_TEST = ""; // a1ba: mittorn, add your signature later
// Load the .so
static {
@ -83,56 +88,15 @@ public class XashActivity extends Activity {
System.loadLibrary("xash");
}
// Shared between this activity and LauncherActivity
public static boolean dumbAntiPDALifeCheck( Context context )
{
if( BuildConfig.DEBUG ||
!XashConfig.CHECK_SIGNATURES )
return false; // disable checking for debug builds
try
{
PackageInfo info = context.getPackageManager()
.getPackageInfo( context.getPackageName(), PackageManager.GET_SIGNATURES );
for( Signature signature: info.signatures )
{
MessageDigest md = MessageDigest.getInstance( "SHA" );
final byte[] signatureBytes = signature.toByteArray();
md.update( signatureBytes );
final String curSIG = Base64.encodeToString( md.digest(), Base64.NO_WRAP );
if( XashConfig.PKG_TEST )
{
if( SIG_TEST.equals(curSIG) )
return false;
}
else
{
if( SIG.equals(curSIG) )
return false;
}
}
}
catch( Exception e )
{
e.printStackTrace();
}
Log.e(TAG, "Please, don't resign our public release builds!");
Log.e(TAG, "If you want to insert some features, rebuild package with ANOTHER package name from git repository.");
return true;
}
// Setup
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.v(TAG, "onCreate()");
super.onCreate(savedInstanceState);
mEngineReady = false;
if( dumbAntiPDALifeCheck(this) )
if( CertCheck.dumbAntiPDALifeCheck(this) )
{
finish();
return;
@ -170,6 +134,268 @@ public class XashActivity extends Activity {
checkWritePermission( basedir );
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
if( resultCode != RESULT_OK )
{
Log.v(TAG, "onActivityResult: result is not OK. ReqCode: " + requestCode + ". ResCode: " + resultCode);
}
else
{
// it's not possible to create dialogs here
// so most work will be done after Activity resuming, in onPostResume()
mReturingWithResultCode = requestCode;
if( requestCode == FPICKER_RESULT )
{
String newBaseDir = resultData.getStringExtra("GetPath");
setNewBasedir( newBaseDir );
setFolderAsk( false ); // don't ask on next run
Log.v(TAG, "Got new basedir from FPicker: " + newBaseDir );
}
else if( requestCode == OPEN_DOCUMENT_TREE_RESULT )
{
Uri uri = resultData.getData();
if( uri != null )
{
getContentResolver().takePersistableUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION );
}
}
}
}
@Override
public void onPostResume()
{
super.onPostResume();
if( mReturingWithResultCode != 0 )
{
if( mReturingWithResultCode == FPICKER_RESULT )
{
String basedir = mPref.getString( "basedir", "/sdcard/xash/" );
checkWritePermission( basedir );
}
else if( mReturingWithResultCode == OPEN_DOCUMENT_TREE_RESULT )
{
String basedir = getStringExtraFromIntent( getIntent(), "basedir", mPref.getString("basedir","/sdcard/xash/"));
Log.v(TAG, "Got permissions. Checking writing again...");
if( nativeTestWritePermission( basedir ) == 0 )
{
Log.v(TAG, "Write test has failed twice!");
String msg = getString(R.string.lollipop_request_permission_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = XashActivity.this;
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
else
{
launchSurfaceAndEngine();
}
}
mReturingWithResultCode = 0;
}
}
// Events
@Override
protected void onPause() {
Log.v(TAG, "onPause()");
if( mEngineReady )
{
// let engine save all configs before exiting.
nativeOnPause();
// wait until Xash will save all configs
mSurface.engineThreadWait();
}
super.onPause();
}
@Override
protected void onResume() {
Log.v(TAG, "onResume()");
super.onResume();
}
@Override
protected void onStop() {
Log.v(TAG, "onStop()");
if( mEngineReady )
{
// let engine properly exit, instead of killing it's thread
nativeOnStop();
// wait for engine
mSurface.engineThreadWait();
}
super.onStop();
}
@Override
protected void onDestroy() {
Log.v(TAG, "onStop()");
if( mEngineReady )
{
// let engine a chance to properly exit
nativeOnDestroy();
// wait until Xash will exit
mSurface.engineThreadJoin();
}
super.onDestroy();
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if( mImmersiveMode != null )
mImmersiveMode.apply();
}
public void setFolderAsk( Boolean b )
{
SharedPreferences.Editor editor = mPref.edit();
editor.putBoolean( "folderask", b );
editor.commit();
}
private void setNewBasedir( String baseDir )
{
SharedPreferences.Editor editor = mPref.edit();
editor.putString( "basedir", baseDir );
editor.commit();
}
private String getStringExtraFromIntent( Intent intent, String extraString, String ifNotFound )
{
String ret = intent.getStringExtra(extraString);
if( ret == null ) ret = ifNotFound;
return ret;
}
private void checkWritePermission( String basedir )
{
Log.v(TAG, "Checking write permissions...");
if( nativeTestWritePermission( basedir ) == 0 )
{
Log.v(TAG, "First check has failed!");
if( sdk > 20 )
{
// OPEN_DOCUMENT_TREE
// first try
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( R.string.lollipop_request_permission_msg )
.setPositiveButton( R.string.lollipop_select_folder_btn, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT_TREE");
XashActivity.this.startActivityForResult(intent, OPEN_DOCUMENT_TREE_RESULT);
}
})
.setCancelable(false)
.show();
}
else if( sdk > 18 )
{
// 4.4 and 4.4W does not allow SD card write at all
String msg = getString(R.string.kitkat_write_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = XashActivity.this;
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
else
{
String msg = getString(R.string.readonly_fs_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
// Read-only filesystem
// Logically should be never reached
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = XashActivity.this;
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
}
else
{
// everything is normal, so launch engine
launchSurfaceAndEngine();
}
}
private void launchSurfaceAndEngine()
{
Log.v(TAG, "Everything is OK. Launching engine...");
setupEnvironment();
InstallReceiver.extractPAK(this, false);
// Set up the surface
mSurface = new EngineSurface(getApplication());
mLayout = new FrameLayout(this);
mLayout.addView(mSurface);
setContentView(mLayout);
SurfaceHolder holder = mSurface.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
if( sdk < 12 )
handler = new JoystickHandler();
@ -191,165 +417,8 @@ public class XashActivity extends Activity {
mVibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = ( mVibrator != null ) && ( mVibrator.hasVibrator() );
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
if( resultCode != RESULT_OK )
{
Log.v(TAG, "onActivityResult: result is not OK. Code: " + requestCode + ". Will exit now");
finish();
}
if( requestCode == FPICKER_RESULT )
{
String newBaseDir = resultData.getStringExtra("GetPath");
setNewBasedir( newBaseDir );
setFolderAsk( false ); // don't ask on next run
checkWritePermission( newBaseDir );
}
else if( requestCode == OPEN_DOCUMENT_TREE_RESULT )
{
String basedir = getStringExtraFromIntent( getIntent(), "basedir", mPref.getString("basedir","/sdcard/xash/"));
if( !nativeTestWritePermission( basedir ) )
{
String msg = getString(R.string.lollipop_request_permission_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = (XashActivity)getActivity();
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
}
}
public void setFolderAsk( Boolean b )
{
SharedPreferences.Editor editor = mPref.edit();
editor.putBoolean( "folderask", b );
editor.commit();
}
private void setNewBasedir( String baseDir )
{
SharedPreferences.Editor editor = mPref.edit();
editor.putBoolean( "basedir", baseDir );
editor.commit();
}
private String getStringExtraFromIntent( Intent intent, String extraString, String ifNotFound )
{
String ret = intent.getStringExtra(extraString);
if( ret == null ) ret = ifNotFound;
return ret;
}
private void checkWritePermission( String basedir )
{
if( !nativeTestWritePermission( basedir ) )
{
Object lock = new Object;
if( sdk > 20 )
{
// OPEN_DOCUMENT_TREE
// first try
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( R.string.lollipop_request_permission_msg )
.setPositiveButton( R.string.lollipop_select_folder_btn, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
Intent intent = new Intent("android.intent.action.OPEN_DOCUMENT_TREE");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getActivity().startActivityForResult(intent, OPEN_DOCUMENT_TREE_RESULT);
}
})
.setCancelable(false)
.show();
}
else if( sdk > 18 )
{
// 4.4 and 4.4W does not allow SD card write at all
String msg = getString(R.string.kitkat_write_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = (XashActivity)getActivity();
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
else
{
String msg = getString(R.string.readonly_fs_fail_msg) + getString(R.string.ask_about_new_basedir_msg);
// Read-only filesystem
// Logically should be never reached
new AlertDialog.Builder(this)
.setTitle( R.string.write_failed )
.setMessage( msg )
.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
XashActivity act = (XashActivity)getActivity();
act.setFolderAsk( true );
act.finish();
}
})
.setCancelable(false)
.show();
}
}
else
{
// everything is normal, so launch engine
launchSurfaceAndEngine();
}
}
private void launchSurfaceAndEngine()
{
setupEnvironment();
InstallReceiver.extractPAK(this, false);
// Set up the surface
mSurface = new EngineSurface(getApplication());
mLayout = new FrameLayout(this);
mLayout.addView(mSurface);
setContentView(mLayout);
SurfaceHolder holder = mSurface.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
mEngineReady = true;
}
private void setupEnvironment()
@ -391,60 +460,6 @@ public class XashActivity extends Activity {
}
}
// Events
@Override
protected void onPause() {
Log.v(TAG, "onPause()");
// let engine save all configs before exiting.
nativeOnPause();
// wait until Xash will save all configs
mSurface.engineThreadWait();
super.onPause();
}
@Override
protected void onResume() {
Log.v(TAG, "onResume()");
super.onResume();
}
@Override
protected void onStop() {
Log.v(TAG, "onStop()");
// let engine properly exit, instead of killing it's thread
nativeOnStop();
// wait for engine
mSurface.engineThreadWait();
super.onStop();
}
@Override
protected void onDestroy() {
Log.v(TAG, "onStop()");
// let engine a chance to properly exit
nativeOnDestroy();
// wait until Xash will exit
mSurface.engineThreadJoin();
super.onDestroy();
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if( mImmersiveMode != null )
mImmersiveMode.apply();
}
public static native int nativeInit(Object arguments);
public static native void nativeQuit();