Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android 6.0 Marshmallow. Cannot write to SD Card

I have an app that uses external storage to store photographs. As required, in its manifest, the following permissions are requested

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

and it uses the following to retrieve the required directory

File sdDir = Environment             .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);  SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd", Locale.US); String date = dateFormat.format(new Date()); storageDir = new File(sdDir, getResources().getString(             R.string.storagedir)             + "-" + date);  // Create directory, error handling if (!storageDir.exists() && !storageDir.mkdirs()) {  ... fails here 

The app works fine on Android 5.1 to 2.3; it has been on Google Play for over a year.

Following an upgrade of one of my testing phones (Android One) to 6, it's now returning an error when trying to create the requisite directory, "/sdcard/Pictures/myapp-yy-mm".

The sd card is configured as "Portable storage". I've formatted the sd card. I've replaced it. I've rebooted. All to no avail.

Also, the built-in android screenshot functionality (via Power+Lower volume) is failing "due to limited storage space, or it isn't allowed by the app or your organisation".

Any ideas?

like image 799
RudyF Avatar asked Oct 15 '15 04:10

RudyF


Video Answer


2 Answers

I faced the same problem. There are two types of permissions in Android:

  • Dangerous (access to contacts, write to external storage...)
  • Normal

Normal permissions are automatically approved by Android while dangerous permissions need to be approved by Android users.

Here is the strategy to get dangerous permissions in Android 6.0

  1. Check if you have the permission granted
  2. If your app is already granted the permission, go ahead and perform normally.
  3. If your app doesn't have the permission yet, ask for user to approve
  4. Listen to user approval in onRequestPermissionsResult

Here is my case: I need to write to external storage.

First, I check if I have the permission:

... private static final int REQUEST_WRITE_STORAGE = 112; ... boolean hasPermission = (ContextCompat.checkSelfPermission(activity,             Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED); if (!hasPermission) {     ActivityCompat.requestPermissions(parentActivity,                 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                 REQUEST_WRITE_STORAGE); } 

Then check the user's approval:

@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {     super.onRequestPermissionsResult(requestCode, permissions, grantResults);     switch (requestCode)     {         case REQUEST_WRITE_STORAGE: {             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)             {                 //reload my activity with permission granted or use the features what required the permission             } else             {                 Toast.makeText(parentActivity, "The app was not allowed to write to your storage. Hence, it cannot function properly. Please consider granting it this permission", Toast.LENGTH_LONG).show();             }         }     }  } 

You can read more about the new permission model here: https://developer.android.com/training/permissions/requesting.html

like image 66
Dũng Trần Trung Avatar answered Oct 05 '22 17:10

Dũng Trần Trung


First i will give you Dangerous Permission List in Android M and Later version

enter image description here enter image description here

Then give you example of how to request for permission in Android M and later version.

I ask user to WRITE_EXTERNAL_STORAGE permission.

First add permission in your android menifest file

Step 1 Declare requestcode

 private static String TAG = "PermissionDemo";  private static final int REQUEST_WRITE_STORAGE = 112;  

Step 2 Add this code when you want ask user for permission

 //ask for the permission in android M     int permission = ContextCompat.checkSelfPermission(this,             Manifest.permission.WRITE_EXTERNAL_STORAGE);      if (permission != PackageManager.PERMISSION_GRANTED) {         Log.i(TAG, "Permission to record denied");          if (ActivityCompat.shouldShowRequestPermissionRationale(this,                 Manifest.permission.WRITE_EXTERNAL_STORAGE)) {             AlertDialog.Builder builder = new AlertDialog.Builder(this);             builder.setMessage("Permission to access the SD-CARD is required for this app to Download PDF.")                     .setTitle("Permission required");              builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {                  public void onClick(DialogInterface dialog, int id) {                     Log.i(TAG, "Clicked");                     makeRequest();                 }             });              AlertDialog dialog = builder.create();             dialog.show();          } else {             makeRequest();         }     }      protected void makeRequest() {         ActivityCompat.requestPermissions(this,                 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},                 REQUEST_WRITE_STORAGE);     } 

Step 3 Add override method for Request

 @Override public void onRequestPermissionsResult(int requestCode,                                        String permissions[], int[] grantResults) {     switch (requestCode) {         case REQUEST_WRITE_STORAGE: {              if (grantResults.length == 0                     || grantResults[0] !=                     PackageManager.PERMISSION_GRANTED) {                  Log.i(TAG, "Permission has been denied by user");              } else {                  Log.i(TAG, "Permission has been granted by user");              }             return;         }     } } 

Note: Do not forget to add permission in menifest file

BEST EXAMPLE BELOW WITH MULTIPLE PERMISSION PLUS COVER ALL SCENARIO

I added comments so you can easily understand.

import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast;  import com.production.hometech.busycoder.R;  import java.util.ArrayList;  public class PermissionInActivity extends AppCompatActivity implements View.OnClickListener {      private static final int REQUEST_PERMISSION_SETTING = 99;     private Button bt_camera;     private static final String[] PARAMS_TAKE_PHOTO = {             Manifest.permission.CAMERA,             Manifest.permission.WRITE_EXTERNAL_STORAGE     };     private static final int RESULT_PARAMS_TAKE_PHOTO = 11;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_permission_in);          bt_camera = (Button) findViewById(R.id.bt_camera);          bt_camera.setOnClickListener(this);      }      @Override     public void onClick(View view) {          switch (view.getId()) {              case R.id.bt_camera:                  takePhoto();                  break;          }     }       /**      * shouldShowRequestPermissionRationale() = This will return true if the user had previously declined to grant you permission      * NOTE :  that ActivityCompat also has a backwards-compatible implementation of      * shouldShowRequestPermissionRationale(), so you can avoid your own API level      * checks.      * <p>      * shouldShowRequestPermissionRationale() =  returns false if the user declined the permission and checked the checkbox to ask you to stop pestering the      * user.      * <p>      * requestPermissions() = request for the permisssiion      */     private void takePhoto() {          if (canTakePhoto()) {              Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();          } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {              Toast.makeText(this, "You should give permission", Toast.LENGTH_SHORT).show();             ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);          } else {             ActivityCompat.requestPermissions(this, netPermisssion(PARAMS_TAKE_PHOTO), RESULT_PARAMS_TAKE_PHOTO);         }      }      //  This method return  permission denied String[] so we can request again     private String[] netPermisssion(String[] wantedPermissions) {         ArrayList<String> result = new ArrayList<>();          for (String permission : wantedPermissions) {             if (!hasPermission(permission)) {                 result.add(permission);             }         }          return (result.toArray(new String[result.size()]));      }      private boolean canTakePhoto() {         return (hasPermission(Manifest.permission.CAMERA) && hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE));     }      /**      * checkSelfPermission() = you can check if you have been granted a runtime permission or not      * ex = ContextCompat.checkSelfPermission(this,permissionString)== PackageManager.PERMISSION_GRANTED      * <p>      * ContextCompat offers a backwards-compatible implementation of checkSelfPermission(), ActivityCompat offers a backwards-compatible      * implementation of requestPermissions() that you can use.      *      * @param permissionString      * @return      */     private boolean hasPermission(String permissionString) {         return (ContextCompat.checkSelfPermission(this, permissionString) == PackageManager.PERMISSION_GRANTED);     }      /**      * requestPermissions() action goes to onRequestPermissionsResult() whether user can GARNT or DENIED those permisssions      *      * @param requestCode      * @param permissions      * @param grantResults      */     @Override     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {         super.onRequestPermissionsResult(requestCode, permissions, grantResults);          if (requestCode == RESULT_PARAMS_TAKE_PHOTO) {              if (canTakePhoto()) {                  Toast.makeText(this, "You can take picture", Toast.LENGTH_SHORT).show();              } else if (!(ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))) {                   final AlertDialog.Builder settingDialog = new AlertDialog.Builder(PermissionInActivity.this);                 settingDialog.setTitle("Permissioin");                 settingDialog.setMessage("Now you need to enable permisssion from the setting because without permission this app won't run properly \n\n  goto -> setting -> appInfo");                 settingDialog.setCancelable(false);                  settingDialog.setPositiveButton("Setting", new DialogInterface.OnClickListener() {                     @Override                     public void onClick(DialogInterface dialogInterface, int i) {                          dialogInterface.cancel();                          Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);                         Uri uri = Uri.fromParts("package", getPackageName(), null);                         intent.setData(uri);                         startActivityForResult(intent, REQUEST_PERMISSION_SETTING);                         Toast.makeText(getBaseContext(), "Go to Permissions to Grant all permission ENABLE", Toast.LENGTH_LONG).show();                      }                 });                 settingDialog.show();                  Toast.makeText(this, "You need to grant permission from setting", Toast.LENGTH_SHORT).show();              }          }      }      @Override     protected void onActivityResult(int requestCode, int resultCode, Intent data) {         super.onActivityResult(requestCode, resultCode, data);          if (requestCode == REQUEST_PERMISSION_SETTING) {              if (canTakePhoto()) {                  Toast.makeText(this, "You can take PHOTO", Toast.LENGTH_SHORT).show();              }          }      }   } 

Special Case for Configuration change

It is possible that the user will rotate the device or otherwise trigger a configuration change while our permission dialog is in the foreground. Since our activity is still visible behind that dialog, we get destroyed and recreated… but we do not want to re-raise the permission dialog again.

That is why we have a boolean, named isInPermission, that tracks whether or not we are in the middle of requesting permissions. We hold onto that value in onSaveInstanceState():

@Override protected void onSaveInstanceState(Bundle outState) {   super.onSaveInstanceState(outState);   outState.putBoolean(STATE_IN_PERMISSION, isInPermission); } 

We restore it in onCreate(). If we do not hold all of the desired permissions, but isInPermission is true, we skip requesting the permissions, since we are in the middle of doing so already.

like image 34
Arpit Patel Avatar answered Oct 05 '22 17:10

Arpit Patel