-3

Env desc: Android version: 6.0.1, DB is created by customer and stored on external storage, the data of Sqlite DB will be collected after task done.

We need read and write the db per different service logical.

Problem:

 Could not open the database in read/write mode. 

java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=10, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.example.xx.sqlitedemo/com.example.xx.sqlitedemo.MainActivity}: 
android.database.sqlite.SQLiteException: not an error (code 0): Could not open the database in read/write mode.

Code :

SQLiteDatabase.openOrCreateDatabase

Permissions are requested:

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

The db file is existed checking by below code in debug mode:

 new File(dbpath).exists();

dbpath is:

"/storage/4B53-98D9/System/System.db"

Dynamic permissions have got, problem still existed.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.xx.sqlitedemo">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MainActivity.java

package com.example.xx.sqlitedemo;

import android.Manifest;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.DefaultDatabaseErrorHandler;
import android.database.sqlite.SQLiteCursor;
import android.database.sqlite.SQLiteCursorDriver;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQuery;
import android.os.Build;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    private static final int EXTERNAL_STORAGE_REQ_CODE = 10;
    private static final int MOUNT_UNMOUNT_FILESYSTEMS = 20;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//        requestMountPermission();
        requestStoragePermission();

        int o = 0;
    }


    private void requestMountPermission() {

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS)) {
                Toast.makeText(this, "please give me the permission", Toast.LENGTH_SHORT).show();
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS},
                        MOUNT_UNMOUNT_FILESYSTEMS);
            }
        } else {
            requestStoragePermission();
            int i = 1;
        }
    }

    public void requestStoragePermission() {

        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Toast.makeText(this, "please give me the permission", Toast.LENGTH_SHORT).show();
            } else {

                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        EXTERNAL_STORAGE_REQ_CODE);
            }
        } else {
            openSQLiteDataBase();
            int i = 1;
        }
    }

    private void openSQLiteDataBase() {
        //            SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase("/storage/4B53-98D9/System/System.db", new CustomCursorFactory(), new CustomDbErrorHandler());
        DatabaseHelper helper = new DatabaseHelper(this,"System.db");
        SQLiteDatabase database = helper.getReadableDatabase();
        int ii = 0;
        Cursor cursor = database.query(null,null,null,null,null,null,null,null);

        database.close();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case EXTERNAL_STORAGE_REQ_CODE:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                    openSQLiteDataBase();

                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.

                } else {

                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
            break;
            case MOUNT_UNMOUNT_FILESYSTEMS:

                break;
        }

    }

    class CustomCursorFactory implements SQLiteDatabase.CursorFactory {
        @Override
        public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) {
            if (Build.VERSION.SDK_INT < 11) {
                return new SQLiteCursor(db, masterQuery, editTable, query);
            } else {
                return new SQLiteCursor(masterQuery, editTable, query);
            }
        }
    }

    class CustomDbErrorHandler implements DatabaseErrorHandler {
        DefaultDatabaseErrorHandler __d = new DefaultDatabaseErrorHandler();
        boolean __once = false;

        @Override
        public void onCorruption(SQLiteDatabase dbObj) {
            if (__once) {
                throw new SQLiteDatabaseCorruptException("db corrupted and cannot be recovered");
            }

            __once = true;
            __d.onCorruption(dbObj);
        }
    }
    class DatabaseContext extends ContextWrapper {

        private static final String DEBUG_CONTEXT = "DatabaseContext";

        public DatabaseContext(Context base) {
            super(base);
        }

        @Override
        public File getDatabasePath(String name)  {
            File sdcard = Environment.getExternalStorageDirectory();
            String path = this.getFilesDir().getAbsolutePath();
            String dbfile = "/storage/4B53-98D9/System/System.db";  //sdcard.getAbsolutePath() + File.separator+ "databases" + File.separator + name;
            if (!dbfile.endsWith(".db")) {
                dbfile += ".db" ;
            }

            File result = new File(dbfile);

            if (!result.getParentFile().exists()) {
                result.getParentFile().mkdirs();
            }

            return result;
        }

        /* this version is called for android devices >= api-11. thank to @damccull for fixing this. */
        @Override
        public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
            return openOrCreateDatabase(name,mode, factory);
        }

        /* this version is called for android devices < api-11 */
        @Override
        public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {
            SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);
            // SQLiteDatabase result = super.openOrCreateDatabase(name, mode, factory);
                Log.w(DEBUG_CONTEXT, "openOrCreateDatabase(" + name + ",,) = " + result.getPath());
            return result;
        }
    }

    public class DatabaseHelper extends SQLiteOpenHelper {
        private static final int DATABASE_VERSION = 1;

        DatabaseHelper(final Context context, String databaseName)  {
            super(new DatabaseContext(context), databaseName, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {

        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }
    }
}

updated at 2017-03-05: The way provided by @greenapps is dangerous:

Have a look at getExternalFilesDirs(). Look at the second item returned.

That would then look like "/storage/4B53-98D9/Android/data/<package>/files/System.db".

When uninstall the app, data was deleted at the same time.

/storage/4B53-98D9/Android/data/<package> was deleted.

Anyone can answer my question?

Thanks in advance!

Bob
  • 1
  • 4

2 Answers2

0

For requesting permission, try out following code..

//check for WRITE_EXTERNAL_STORAGE permission
        int hasAccessExternalStoragePermission = ContextCompat.checkSelfPermission(LoginActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (hasAccessExternalStoragePermission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(LoginActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_CODE_ASK_PERMISSIONS);
    }

And handle it like following..

@Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_PERMISSIONS:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.e(TAG, "Permission Granted....");
                    createFile();
                }else{
                     Log.e(TAG, "Permission Not Granted....");
                }
        }
    }

I guess the problem is coming because you are using shouldShowRequestPermissionRationale this method. Please read usage if this method here.

Rohit
  • 2,646
  • 6
  • 27
  • 52
0

The SD card is read only so that is why you get the message that it cannot be opened in write mode.

You could try to put the db file in an app specific directory on the SD card where your app can write.

Have a look at getExternalFilesDirs(). Look at the second item returned.

That would then look like "/storage/4B53-98D9/Android/data/<package>/files/System.db".

greenapps
  • 11,154
  • 2
  • 16
  • 19
  • @gerenapps, Thanks for your suggestion. Yes your are right, it works now. But i can't provide it to customer, do you have any other suggestions? – Bob Mar 10 '17 at 09:45
  • Why you suddenly cant provide to customer? – greenapps Mar 10 '17 at 09:55
  • customer will config db and it's path by self, customer will be confused to save data to a path, which seems like Android/data/com.xx.xxx.xx/files/system.db. Your solution like a workaround for customer, it is fine to developer. – Bob Mar 11 '17 at 00:28
  • when we uninstall the app, which read "/storage/4B53-98D9/Android/data//files/System.db", the dir "/storage/4B53-98D9/Android/data/" was deleted. Do you know other way for my question? – Bob Mar 15 '17 at 06:02
  • What you could do every time your app is closed make a copy of the database file to the directory on the SD card which you first had in mind. Using the Storage Access Framework you can write on the whole SD card. Or teach the database helper class and other sqlite stuff to read/write to content schemes. – greenapps Mar 15 '17 at 08:36