Slide 1

Slide 1 text

+ShintaroKatafuchi Managing Runtime Permissions @hotchemi

Slide 2

Slide 2 text

• +ShintaroKatafuchi • @hotchemi • Working at Recruit group • Member of DroidKaigi

Slide 3

Slide 3 text

• The biggest android conf in Japan • 2016.02.18 - 2016.02.19 • #droidkaigi • https://droidkaigi.github.io/2016/en/

Slide 4

Slide 4 text

Runtime Permissions

Slide 5

Slide 5 text

• Before Marshmallow • Grant permissions at install time • Check additional permission
 when the app is updated • “Hate it. One star!!!”

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

• Never ask again • Once user check and denied,
 checkSelfPermission always 
 return PERMISSION_DENIED • targetSdkVersion >= 23 • Lead user to settings screen

Slide 9

Slide 9 text

Intent  intent  =            new  Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
 Uri  uri  =  Uri.fromParts("package",  getPackageName(),  null);
 intent.setData(uri);
 startActivity(intent); • Never ask again

Slide 10

Slide 10 text

• Protection level • PROTECTION_NORMAL • PROTECTION_DANGEROUS • PROTECTION_SIGNATURE • PROTECTION_SIGNATURE_OR_SYSTEM (Deprecated) • PROTECTION_FLAG_XXX
 etc…

Slide 11

Slide 11 text

• Normal permissions • ACCESS_NETWORK_STATE • BLUETOOTH • INTERNET • NFC • VIBRATE • WAKE_LOCK • SET_ALARM
 etc…

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

• Dangerous permissions • Includes custom permission

Slide 14

Slide 14 text

• 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

Slide 15

Slide 15 text

• 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

Slide 16

Slide 16 text

• 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.
                }
        }
 }

Slide 17

Slide 17 text

• Other tips • Permission setting is shared among same sharedUserId • Manifest trick • Use checker to find dangerous permissions • https://github.com/hotchemi/m-permissions-checker 
   $  cd     $  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  

Slide 18

Slide 18 text

How to write code?

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

targetSdkVersion >= 23

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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;

Slide 23

Slide 23 text

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"};  

Slide 24

Slide 24 text

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);          }

Slide 25

Slide 25 text

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;
 }

Slide 26

Slide 26 text

targetSdkVersion < 23

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

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);          }   }   }

Slide 29

Slide 29 text

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)  {   }

Slide 30

Slide 30 text

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)  {   }

Slide 31

Slide 31 text

@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;
        }
      

Slide 32

Slide 32 text

Design pattern

Slide 33

Slide 33 text

• Best practices • Consider using an intent • Only ask for permissions you need • Explain why you need permissions

Slide 34

Slide 34 text

https://www.google.com/design/spec/patterns/permissions.html

Slide 35

Slide 35 text

• Educate up-front • If your app has a “welcome,”
 use it to explain what your app
 does and why unexpected
 permissions will be requested

Slide 36

Slide 36 text

• Ask up-front • Ask for critical and obvious 
 permissions on first launch • messaging app requests 
 SMS permissions up-front,
 that makes sense

Slide 37

Slide 37 text

• Educate in context • Explaining a permission in 
 context helps draw user 
 interest and improve 
 comprehension

Slide 38

Slide 38 text

• Ask in context • Wait until a feature is invoked 
 to request permission • allow a permission when they
 want to use the feature

Slide 39

Slide 39 text

Alright, go ahead!!!

Slide 40

Slide 40 text

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;
 }   }

Slide 41

Slide 41 text

OK, one more…

Slide 42

Slide 42 text

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;
 }   }

Slide 43

Slide 43 text

Just tired…

Slide 44

Slide 44 text

No worries.

Slide 45

Slide 45 text

• 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

Slide 46

Slide 46 text

• Simple annotation based API • @RuntimePermissions ✔ • @NeedsPermission ✔ • @OnDeniedPermission • @OnShowRationale • @NeverAskAgain

Slide 47

Slide 47 text

apply  plugin:  'android-­‐apt'   dependencies  {
        compile  “com.github.hotchemi:permissionsdispatcher:2.0.1”          apt  “com.github.hotchemi:permissionsdispatcher-­‐processor:2.0.1”
 }

Slide 48

Slide 48 text

• @RuntimePermissions • Activity • Fragment • Fragment(support v4) public  class  MainActivity  extends  AppCompatActivity{   } @RuntimePermissions

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

• @OnPermissionDenied • Single permission • Multiple permissions 
 void  onCameraDenied()  {
        Toast.makeText(this,  R.string.permission_camera_denied,  Toast.LENGTH_LONG).show();
 }   @OnPermissionDenied(Manifest.permission.CAMERA)  

Slide 51

Slide 51 text

• @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();
 }

Slide 52

Slide 52 text

• @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)  

Slide 53

Slide 53 text

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);

Slide 54

Slide 54 text

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  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

Slide 55

Slide 55 text

Conclusion

Slide 56

Slide 56 text

• 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!!!

Slide 57

Slide 57 text

+ShintaroKatafuchi Thanks, any questions? @hotchemi