Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Managing runtime permissions

@hotchemi
December 01, 2015

Managing runtime permissions

droidcon.pl 2015

@hotchemi

December 01, 2015
Tweet

More Decks by @hotchemi

Other Decks in Programming

Transcript

  1. • The biggest android conf in Japan • 2016.02.18 -

    2016.02.19 • #droidkaigi • https://droidkaigi.github.io/2016/en/
  2. • Before Marshmallow • Grant permissions at install time •

    Check additional permission
 when the app is updated • “Hate it. One star!!!”
  3. • After Marshmallow • Grant permissions at runtime • User

    can revoke permissions
 from settings screen • Process is restarted
  4. • After Marshmallow • Grant permissions at runtime • User

    can revoke permissions
 from settings screen • Process is restarted
  5. • Never ask again • Once user check and denied,


    checkSelfPermission always 
 return PERMISSION_DENIED • targetSdkVersion >= 23 • Lead user to settings screen
  6. Intent  intent  =            new  Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);


    Uri  uri  =  Uri.fromParts("package",  getPackageName(),  null);
 intent.setData(uri);
 startActivity(intent); • Never ask again
  7. • Protection level • PROTECTION_NORMAL • PROTECTION_DANGEROUS • PROTECTION_SIGNATURE •

    PROTECTION_SIGNATURE_OR_SYSTEM (Deprecated) • PROTECTION_FLAG_XXX
 etc…
  8. • Normal permissions • ACCESS_NETWORK_STATE • BLUETOOTH • INTERNET •

    NFC • VIBRATE • WAKE_LOCK • SET_ALARM
 etc…
  9. Permission groups Permissions CALENDAR READ_CALENDAR WRITE_CALENDAR CAMERA CAMERA CONTACTS READ_CONTACTS

    WRITE_CONTACTS GET_ACCOUNTS LOCATION ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION MICROPHONE RECORD_AUDIO PHONE READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS SENSORS BODY_SENSORS SMS SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS STORAGE READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE • Dangerous permissions
  10. • Permission groups • Same group permissions are granted at

    once
 ex) READ_CALENDAR, WRITE_CALENDAR Request read calendar permission Grant calendar group Show permission dialog Press allow button Request write calendar permission Calendar is granted already! Returns GRANTED
  11. • Permission groups • “Never ask again” is the same


    ex) READ_CALENDAR, WRITE_CALENDAR Request read calendar permission Deny calendar group Show permission dialog Press deny button with check Request write calendar permission Calendar is denied already! Returns DENIED
  12. • Special permissions • SYSTEM_ALERT_WINDOW • WRITE_SETTINGS @Override
 protected  void

     onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        if  (!Settings.canDrawOverlays(this))  {
                Intent  intent  =  new  Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                                Uri.parse("package:"  +  getPackageName()));
                startActivityForResult(intent,  REQUEST_CODE_PERMISSION_SYSTEM_ALERT_WINDOW);
        }
 }   @Override
 protected  void  onActivityResult(int  requestCode,  int  resultCode,  Intent  data)  {
        if  (requestCode  ==  REQUEST_CODE_PERMISSION_SYSTEM_ALERT_WINDOW)  {
                if  (Settings.canDrawOverlays(this))  {
                        //  yay!
                }  else  {
                        //  denied.
                }
        }
 }
  13. • Other tips • Permission setting is shared among same

    sharedUserId • Manifest trick • Use checker to find dangerous permissions • https://github.com/hotchemi/m-permissions-checker <uses-­‐permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="18"/>
 <uses-­‐permission-­‐sdk-­‐23            android:name="android.permission.ACCESS_FINE_LOCATION"/>   $  cd  <root  your  app>   $  python  permissions_checker.py   >  Searching  file:  /Users/hoge/test/data/AndroidManifest.xml   >  Unfortunately,  you  have  to  handle  these  permissions  in  MNC.   >  android.permission.READ_CALENDAR   >  android.permission.WRITE_CALENDAR   >  android.permission.CAMERA  
  14. targetSdkVersion / OS sdk >= 23 / M sdk <

    23 / M sdk >= 23 / L Grant permissions Runtime Install Install Never ask again Yes No No Can revoke from settings Yes Yes No
  15. public  class  MainActivity  extends  AppCompatActivity  {   
 @Override  

    protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);   
        showCamera();                  
    
     }   }
  16. public  class  MainActivity  extends  AppCompatActivity  {   
 @Override  

    protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          showCamera();   
 
    
     }   } private  static  final  int  REQUEST_SHOWCAMERA  =  0;
  17. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   
 @Override   protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          showCamera();   
 
    
     }   } private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};  
  18. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override   protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);           
 
    
     }   }        if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }
  19. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override
 protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }   }   } @Override   public  void  onRequestPermissionsResult(int  requestCode,  String[]  permissions,  int[]  grantResults)  {
 switch  (requestCode)  {
        case  REQUEST_SHOWCAMERA:
        if  (PermissionUtils.verifyPermissions(grantResults))  {  //  check  all  permissions  are  granted
            showCamera();
        }  else  {              if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {                  //  show  denied  action              }  else  {                  //  never  ask  again              }          }
        break;
 }
  20. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override
 protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          //  expect  PERMISSION_DENIED          if  (ContextCompat.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }   }   }
  21. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override
 protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          //  expect  PERMISSION_DENIED          if  (ContextCompat.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }   }   }        //  some  compete  class  return  PERMISSION_GRANTED!          if  (ContextCompat.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {   }
  22. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override
 protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          //  expect  PERMISSION_DENIED          if  (ContextCompat.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }   }   }        //  with  PermissionChecker,  returns  PERMISSION_DENIED_APP_OP          if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {   }
  23. @Override   public  void  onRequestPermissionsResult(int  requestCode,  String[]  permissions,  int[]  grantResults)

     {
 switch  (requestCode)  {
        case  REQUEST_SHOWCAMERA:          if  (!PermissionUtils.hasSelfPermissions(this,  PERMISSION_SHOWCAMERA))  {  //  results  are  always  granted…
            //  show  denied  action
            return;
        }
        if  (PermissionUtils.verifyPermissions(grantResults))  {
            showCamera();
        }  else  {              if  (Actile(this,  PERMISSION_SHOWCAMERA))  {                  //  show  denied  action          }
        break;
 }        if  (!PermissionUtils.hasSelfPermissions(this,  PERMISSION_SHOWCAMERA))  {  //  results  are  always  granted…
            //  show  denied  action
            return;
        }
      
  24. • Best practices • Consider using an intent • Only

    ask for permissions you need • Explain why you need permissions
  25. • Educate up-front • If your app has a “welcome,”


    use it to explain what your app
 does and why unexpected
 permissions will be requested
  26. • Ask up-front • Ask for critical and obvious 


    permissions on first launch • messaging app requests 
 SMS permissions up-front,
 that makes sense
  27. • Educate in context • Explaining a permission in 


    context helps draw user 
 interest and improve 
 comprehension
  28. • Ask in context • Wait until a feature is

    invoked 
 to request permission • allow a permission when they
 want to use the feature
  29. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{"android.permission.CAMERA"};   
 @Override   protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }   }   @Override   public  void  onRequestPermissionsResult(int  requestCode,  String[]  permissions,  int[]  grantResults)  {
 switch  (requestCode)  {
        case  REQUEST_SHOWCAMERA:          if  (!PermissionUtils.hasSelfPermissions(this,  PERMISSION_SHOWCAMERA))  {
            //  show  denied  action
            return;
        }
        if  (PermissionUtils.verifyPermissions(grantResults))  {
            showCamera();
        }  else  {              if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {                  //  show  denied  action              }  else  {                  //  never  ask  again              }          }
        break;
 }   }
  30. public  class  MainActivity  extends  AppCompatActivity  {   private  static  final

     int  REQUEST_SHOWCAMERA  =  0;   private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]{“android.permission.CAMERA"};   private  static  final  int  REQUEST_SHOWCONTACT  =  0;   private  static  final  String[]  PERMISSION_SHOWCONTACT  =  new  String[]{“android.permission.READ_CONTACT"};   
 @Override   protected  void  onCreate(Bundle  savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);          if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCAMERA)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {
                //  show  rationale…
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);          }          if  (PermissionChecker.checkSelfPermissions(this,  PERMISSION_SHOWCONTACT)  ==  PERMISSION_GRANTED)  {
            showCamera();          }  else  {
            if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCONTACT))  {
                //  show  rationale…
            }
            ActivityCompat.requestPermissions(this,  PERMISSION_SHOWCONTACT,  REQUEST_SHOWCONTACT);          }   }   @Override   public  void  onRequestPermissionsResult(int  requestCode,  String[]  permissions,  int[]  grantResults)  {
 switch  (requestCode)  {
        case  REQUEST_SHOWCAMERA:          if  (!PermissionUtils.hasSelfPermissions(this,  PERMISSION_SHOWCAMERA))  {
            onCameraDenied();
            return;
        }
        if  (PermissionUtils.verifyPermissions(grantResults))  {
            //  show  camera
        }  else  {              if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCAMERA))  {                  //  show  denied  action              }  else  {                  //  never  ask  again              }          }
        break;          case  REQUEST_SHOWCONTACT:          if  (!PermissionUtils.hasSelfPermissions(this,  PERMISSION_SHOWCONTACT))  {
            //  show  denied  action
            return;
        }
        if  (PermissionUtils.verifyPermissions(grantResults))  {
            //  show  camera
        }  else  {              if  (ActivityCompat.shouldShowRequestPermissionRationale(this,  PERMISSION_SHOWCONTACT))  {                  //  show  denied  action              }  else  {                  //  never  ask  again              }          }
        break;
 }   }
  31. • PermissionsDispatcher • Generate codes with annotation processing • supported

    on API levels 4 and up • Processor module is written with Kotlin • Developed by I and @marcelschnelle • https://github.com/hotchemi/PermissionsDispatcher
  32. • Simple annotation based API • @RuntimePermissions ✔ • @NeedsPermission

    ✔ • @OnDeniedPermission • @OnShowRationale • @NeverAskAgain
  33. apply  plugin:  'android-­‐apt'   dependencies  {
        compile

     “com.github.hotchemi:permissionsdispatcher:2.0.1”          apt  “com.github.hotchemi:permissionsdispatcher-­‐processor:2.0.1”
 }
  34. • @RuntimePermissions • Activity • Fragment • Fragment(support v4) public

     class  MainActivity  extends  AppCompatActivity{   } @RuntimePermissions
  35. • @NeedsPermission • Single permission • Multiple permissions void  showCamera()

     {
        getSupportFragmentManager().beginTransaction()
                        .replace(R.id.sample_content_fragment,  CameraPreviewFragment.newInstance())
                        .addToBackStack("camera")
                        .commitAllowingStateLoss();
 } @NeedsPermission(Manifest.permission.CAMERA)
  36. • @OnPermissionDenied • Single permission • Multiple permissions 
 void

     onCameraDenied()  {
        Toast.makeText(this,  R.string.permission_camera_denied,  Toast.LENGTH_LONG).show();
 }   @OnPermissionDenied(Manifest.permission.CAMERA)  
  37. • @OnShowRationale • Asynchronous support 
 @OnShowRationale(Manifest.permission.CAMERA) 
 
 void

     showRationaleForCamera(final  PermissionRequest  request)  {
        new  AlertDialog.Builder(this)
                        .setPositiveButton(R.string.button_allow,  new  DialogInterface.OnClickListener()  {
                                @Override  public  void  onClick(@NonNull  DialogInterface  dialog,  int  which)  {
                                        request.proceed();
                                }                          })
                        .setNegativeButton(R.string.button_deny,  new  DialogInterface.OnClickListener()  {
                                @Override  public  void  onClick(@NonNull  DialogInterface  dialog,  int  which)  {
                                        request.cancel();
                                }                          })
                        .setMessage(R.string.permission_camera_rationale)
                        .show();
 }
  38. • @NeverAskAgain • Single permission • Multiple permissions • Coming

    soon… 
 void  onNeverAskAgain()  {
        Toast.makeText(this,  R.string.permission_camera_neveraskagain,  Toast.LENGTH_LONG).show();
 }   @NeverAskAgain(Manifest.permission.CAMERA)  
  39. package  permissions.dispatcher.sample;
 
 @RuntimePermissions
 public  class  MainActivity  extends  AppCompatActivity  implements

     View.OnClickListener  {
 
        @Override
        protected  void  onCreate(Bundle  savedInstanceState)  {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                findViewById(R.id.button_camera).setOnClickListener(this);
        }
 
        @Override
        public  void  onClick(@NonNull  View  v)  {
                switch  (v.getId())  {
                        case  R.id.button_camera:
                                MainActivityPermissionsDispatcher.showCameraWithCheck(this);
                                break;
                }
        }
 
        @Override
        public  void  onRequestPermissionsResult(int  requestCode,  @NonNull  String[]  permissions,  @NonNull  int[]  grantResults)  {                super.onRequestPermissionsResult(requestCode,  permissions,  grantResults);
                MainActivityPermissionsDispatcher.onRequestPermissionsResult(this,  requestCode,  grantResults);
        }   
 } 
 
 
 
                                MainActivityPermissionsDispatcher.showCameraWithCheck(this);   
 
 
                MainActivityPermissionsDispatcher.onRequestPermissionsResult(this,  requestCode,  grantResults);
  40. package  permissions.dispatcher.sample;
 
 final  class  MainActivityPermissionsDispatcher  {
    private  static

     final  int  REQUEST_SHOWCAMERA  =  0;
 
    private  static  final  String[]  PERMISSION_SHOWCAMERA  =  new  String[]  {"android.permission.CAMERA"};
 
    private  MainActivityPermissionsDispatcher()  {
    }
 
    static  void  showCameraWithCheck(MainActivity  target)  {
        if  (PermissionUtils.hasSelfPermissions(target,  PERMISSION_SHOWCAMERA))  {
            target.showCamera();
        }  else  {
            if  (PermissionUtils.shouldShowRequestPermissionRationale(target,  PERMISSION_SHOWCAMERA))  {
                target.showRationaleForCamera(new  ShowCameraPermissionRequest(target));
            }  else  {
                ActivityCompat.requestPermissions(target,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);
            }
        }
    }
 
    static  void  onRequestPermissionsResult(MainActivity  target,  int  requestCode,  int[]  grantResults)  {
        switch  (requestCode)  {
            case  REQUEST_SHOWCAMERA:
            if  (PermissionUtils.verifyPermissions(grantResults))  {
                target.showCamera();
            }  else  {
                target.onCameraDenied();
            }
            break;
            default:
            break;
        }
    }
 
    private  static  final  class  ShowCameraPermissionRequest  implements  PermissionRequest  {
        private  final  WeakReference<MainActivity>  weakTarget;
 
        private  ShowCameraPermissionRequest(MainActivity  target)  {
            this.weakTarget  =  new  WeakReference<>(target);
        }
 
        @Override
        public  void  proceed()  {
            MainActivity  target  =  weakTarget.get();
            if  (target  ==  null)  return;
            ActivityCompat.requestPermissions(target,  PERMISSION_SHOWCAMERA,  REQUEST_SHOWCAMERA);
        }
 
        @Override
        public  void  cancel()  {
            MainActivity  target  =  weakTarget.get();
            if  (target  ==  null)  return;
            target.onCameraDenied();
        }
    }
 } Generated code
  41. • You can’t run away from runtime permissions • But

    runtime permissions is difficult to handle • Follow good practice to provide better user experience • Enjoy PermissionsDispatcher!!!