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

New Permissions model on a "Nice" way

New Permissions model on a "Nice" way

In this presentation I will show you how the new permissions model introduced in Android 6.0 works, and how we can educate our users, so they can understand why are we asking for certain permission/s.
To make it even more complete and since educating our users adds more complexity to the new permissions model implementation, I will show you how we can follow Clean architecture and architectural patterns to make your implementation better, decoupled and easy to test, in just a word, "nice".

I've created a sample app to show you what I mentioned above, you can find it here: https://goo.gl/JI15TU feel free to check, clone, modify use, etc. is under Apache License v2.

A video with a demo of the sample app can be found here: https://goo.gl/svx3Vl

Cesar Valiente

October 03, 2015
Tweet

More Decks by Cesar Valiente

Other Decks in Programming

Transcript

  1. Who is this guy? Image Placeholder César Valiente Android Engineer

    @Wunderlist (@Microsoft) Android Google Developer Expert (GDE) +CesarValiente @CesarValiente
  2. Problems • Installing apps based in trust. • Using permissions

    wildly. • Accepted permissions that maybe the user will never use (the related functionality won’t be ever used). First and crucial barrier, many users won’t download the app.
  3. What’s new here? • Permission groups. • “Normal” permissions. •

    Permissions are accepted or denied in runtime. • Permissions can be accepted or denied in settings. • Apps have to deal with denied permissions. • Maybe you don’t have to ask for permissions. • User education.
  4. “Normal” permissions Are designated as PROTECTION_NORMAL, no great risk to

    the user’s privacy or security. We have 37 permissions here. Widely used permissions, as INTERNET, VIBRATE and RECEIVE_BOOT_COMPLETED are in this group, so no need to ask for them.
  5. Apps have to deal with denied permissions • App with

    targetSDK <23 on an Android 6.0 device won’t crash. Functions which need permissions will return an empty state value. • App with targetSDK >= 23 not ready to work with permissions, it will crash. Be careful to set your app to targetSDK 23. Implements the new permissions model before to release it. If we deny a permission in settings…
  6. Do I have to ask for permission? • We can

    still use Intents without the need to request a permission for certain functions. • ACTION_INSERT, ACTION_IMAGE_CAPTURE, ACTION_VIDEO_CAPTURE, ACTION_PICK, ACTION_VIEW, ACTION_EDIT, ACTION_DIAL, ACTION_CALL, ACTION_SENDTO, ACTION_SEND, ACTION_SEND_MULTIPLE.
  7. User education Educate up-front Ask up-front Educate in context Ask

    in context Critical Clear Secondary Unclear
  8. How can we ask for permission? if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED)

    {
 
 // Should we show an explanation?
 if (shouldShowRequestPermissionRationale(
 Manifest.permission.READ_CONTACTS)) {
 // Explain to the user why we need to read the contacts
 }
 
 requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
 MY_PERMISSIONS_REQUEST_READ_CONTACTS);
 
 // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
 // app-defined int constant
 
 return;
 }
  9. How can we manage the result? @Override
 public void onRequestPermissionsResult(int

    requestCode,
 String permissions[], int[] grantResults) {
 switch (requestCode) {
 case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 
 // permission was granted, yay! do the
 // calendar task you need to do.
 
 } else {
 
 // permission denied, boo! Disable the
 // functionality that depends on this permission.
 }
 return;
 }
 
 // other 'switch' lines to check for other
 // permissions this app might request
 }
 }
  10. Two ways to ask for the same • Using the

    framework (targetSdkVersion 23). • Check first for the correct sdk version we are using. • Using the support library v4 or v13 revision 23. • Methods (v4) check internally for the correct sdk version. • Adds support for fragments (v13) and apps that use IPC (v4).
  11. User wants to do an action that involves a permission

    Does the app have the required permission?
  12. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission)
  13. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) yes Handle permission result
  14. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result
  15. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result Educate your users up to you (permission denied)
  16. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result Educate your users up to you (permission denied) Has the user already been asked for this permission? no
  17. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result Educate your users up to you (permission denied) Has the user already been asked for this permission? no
  18. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result Educate your users up to you (permission denied) Has the user already been asked for this permission? no Ask for permission no
  19. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result Educate your users up to you (permission denied) Has the user already been asked for this permission? no Ask for permission no
  20. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result yes/no Educate your users up to you (permission denied) Has the user already been asked for this permission? no Ask for permission no
  21. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result yes/no Educate your users up to you (permission denied) Has the user already been asked for this permission? no Has “Don’t ask again” been checked? yes Ask for permission no
  22. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result yes/no Educate your users up to you (permission denied) Has the user already been asked for this permission? no Has “Don’t ask again” been checked? yes Ask for permission no Show rationale info no
  23. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result yes/no Educate your users up to you (permission denied) Has the user already been asked for this permission? no Has “Don’t ask again” been checked? yes Ask for permission no Show rationale info no up to you (you can add a “try it” option)
  24. User wants to do an action that involves a permission

    Does the app have the required permission? checkSelfPermission (permission) shouldShowRequestPermission Rationale(permission) requestPermissions(new String[] { permission1.. permissionN}, requestCode) onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) yes Handle permission result yes/no Educate your users up to you (permission denied) Has the user already been asked for this permission? no Has “Don’t ask again” been checked? yes Ask for permission no Educate your users yes (but is up to you) Show rationale info no up to you (you can add a “try it” option)
  25. Is it easy? • Yes, the implementation is simple, but

    the flow is a mess. • Educate to your users adds more difficult to it. • Reusing permissions in several parts of your app makes this even more complicated. • Then?
  26. We can do it on a “nice” way • Follow

    Clean architecture, but we have to feel free to adapt it following our requirements, tastes, preferences, etc. • Architectural patterns. • Activity manages permissions. Fragments just do stuff. • The permissions functionality is easy to test. • Sample app: PermissionsSample (under Apache License v2).
  27. What does the sample app do? • Shows how the

    new permission model works. • Asks for three permissions. • Educate users. • Applies clean architecture and architectural patterns. • Shows how you can apply this approach to your app/ architecture.
  28. Action PermissionAction (Interface) ActivityPermission ActionImpl SupportPermission ActionImpl public interface PermissionAction

    {
 
 boolean hasSelfPermission(String permission);
 
 void requestPermission(String permission, int requestCode);
 
 boolean shouldShowRequestPermissionRationale(String permission);
 }
  29. Action PermissionAction (Interface) ActivityPermission ActionImpl SupportPermission ActionImpl public interface PermissionAction

    {
 
 boolean hasSelfPermission(String permission);
 
 void requestPermission(String permission, int requestCode);
 
 boolean shouldShowRequestPermissionRationale(String permission);
 } public class ActivityPermissionActionImpl implements PermissionAction {
 
 private Activity activity;
 
 public ActivityPermissionActionImpl(Activity activity) {
 this.activity = activity;
 }
 
 @Override
 public boolean hasSelfPermission(String permission) {
 if (!CommonUtils.isMarshmallowOrHigher()) {
 return true;
 }
 return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
 }
 
 @Override
 public void requestPermission(String permission, int requestCode) {
 activity.requestPermissions(new String[] { permission }, requestCode);
 }
 
 @Override
 public boolean shoulShowRequestPermissionRationale(String permission) {
 return activity.shouldShowRequestPermissionRationale(permission);
 }
 }
  30. Action PermissionAction (Interface) ActivityPermission ActionImpl SupportPermission ActionImpl public interface PermissionAction

    {
 
 boolean hasSelfPermission(String permission);
 
 void requestPermission(String permission, int requestCode);
 
 boolean shouldShowRequestPermissionRationale(String permission);
 } public class ActivityPermissionActionImpl implements PermissionAction {
 
 private Activity activity;
 
 public ActivityPermissionActionImpl(Activity activity) {
 this.activity = activity;
 }
 
 @Override
 public boolean hasSelfPermission(String permission) {
 if (!CommonUtils.isMarshmallowOrHigher()) {
 return true;
 }
 return activity.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
 }
 
 @Override
 public void requestPermission(String permission, int requestCode) {
 activity.requestPermissions(new String[] { permission }, requestCode);
 }
 
 @Override
 public boolean shoulShowRequestPermissionRationale(String permission) {
 return activity.shouldShowRequestPermissionRationale(permission);
 }
 } public class SupportPermissionActionImpl implements PermissionAction {
 
 private Activity activity;
 
 public SupportPermissionActionImpl(Activity activity) {
 this.activity = activity;
 }
 
 @Override
 public boolean hasSelfPermission(String permission) {
 return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED;
 }
 
 @Override
 public void requestPermission(String permission, int requestCode) {
 ActivityCompat.requestPermissions(activity, new String[] { permission }, requestCode); 
 }
 
 @Override
 public boolean shouldShowRequestPermissionRationale(String permission) {
 return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission);
 }
 }
  31. Interaction App (presentation) BaseActivity MainActivity ContactList Fragment Contact Fragment Action

    XXXPermissionAction Impl PermissionPresenter PermissionCallbacks ContactPresenter ContactListPresenter MainView
  32. public interface MainView {
 //Rest of the methods have been

    removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }
  33. public interface PermissionCallbacks {
 void permissionAccepted(int action);
 
 void permissionDenied(int

    action);
 
 void showRationale(int action);
 } public interface MainView {
 //Rest of the methods have been removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }
  34. public interface PermissionCallbacks {
 void permissionAccepted(int action);
 
 void permissionDenied(int

    action);
 
 void showRationale(int action);
 } public class MainActivity extends BaseActivity implements MainView, PermissionCallbacks {
 private PermissionPresenter permissionPresenter; @Override
 protected void onCreate(Bundle savedInstanceState) {
 //Removed content. Here it would be better to use a dependency injector or a service locator //to inject the dependencies. PermissionActionFactory permissionActionFactory = new PermissionActionFactory(this);
 PermissionAction permissionAction = permissionActionFactory.getPermissionAction(SUPPORT_IMPL);
 permissionPresenter = new PermissionPresenter(permissionAction, this); 
 //Removed content (check code in the repo)
 } public interface MainView {
 //Rest of the methods have been removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }
  35. public class MainActivity extends BaseActivity implements MainView, PermissionCallbacks {
 private

    PermissionPresenter permissionPresenter; @Override
 protected void onCreate(Bundle savedInstanceState) {
 //Removed content. Here it would be better to use a dependency injector or a service locator //to inject the dependencies. PermissionActionFactory permissionActionFactory = new PermissionActionFactory(this);
 PermissionAction permissionAction = permissionActionFactory.getPermissionAction(SUPPORT_IMPL);
 permissionPresenter = new PermissionPresenter(permissionAction, this); 
 //Removed content (check code in the repo)
 } @Override
 public void requestReadContacts() {
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 }
 
 //The body of the next functions has been deleted here @Override
 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { } public interface PermissionCallbacks {
 void permissionAccepted(int action);
 
 void permissionDenied(int action);
 
 void showRationale(int action);
 } public interface MainView {
 //Rest of the methods have been removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }
  36. public class MainActivity extends BaseActivity implements MainView, PermissionCallbacks {
 private

    PermissionPresenter permissionPresenter; @Override
 protected void onCreate(Bundle savedInstanceState) {
 //Removed content. Here it would be better to use a dependency injector or a service locator //to inject the dependencies. PermissionActionFactory permissionActionFactory = new PermissionActionFactory(this);
 PermissionAction permissionAction = permissionActionFactory.getPermissionAction(SUPPORT_IMPL);
 permissionPresenter = new PermissionPresenter(permissionAction, this); 
 //Removed content (check code in the repo)
 } @Override
 public void requestReadContacts() {
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 }
 
 //The body of the next functions has been deleted here @Override
 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { } @Override
 public void permissionAccepted(int action) { } @Override
 public void permissionDenied(int action) { } @Override
 public void showRationale(int action) { } } public interface PermissionCallbacks {
 void permissionAccepted(int action);
 
 void permissionDenied(int action);
 
 void showRationale(int action);
 } public interface MainView {
 //Rest of the methods have been removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }
  37. public class PermissionPresenter {
 
 public static final int ACTION_READ_CONTACTS

    = 1;
 public static final int ACTION_SAVE_IMAGE = 2;
 public static final int ACTION_SEND_SMS = 3;
 
 private PermissionAction permissionAction;
 private PermissionCallbacks permissionCallbacks;
 
 public PermissionPresenter(PermissionAction permissionAction, PermissionCallbacks permissionCallbacks) {
 this.permissionAction = permissionAction;
 this.permissionCallbacks = permissionCallbacks;
 }
  38. public class PermissionPresenter {
 
 public static final int ACTION_READ_CONTACTS

    = 1;
 public static final int ACTION_SAVE_IMAGE = 2;
 public static final int ACTION_SEND_SMS = 3;
 
 private PermissionAction permissionAction;
 private PermissionCallbacks permissionCallbacks;
 
 public PermissionPresenter(PermissionAction permissionAction, PermissionCallbacks permissionCallbacks) {
 this.permissionAction = permissionAction;
 this.permissionCallbacks = permissionCallbacks;
 } public void requestReadContactsPermission(int action) {
 checkAndRequestPermission(action, Manifest.permission.READ_CONTACTS);
 }
 
 public void requestReadContactsPermissionAfterRationale(int action) {
 permissionAction.requestPermission(Manifest.permission.READ_CONTACTS, action);
 }
  39. public class PermissionPresenter {
 
 public static final int ACTION_READ_CONTACTS

    = 1;
 public static final int ACTION_SAVE_IMAGE = 2;
 public static final int ACTION_SEND_SMS = 3;
 
 private PermissionAction permissionAction;
 private PermissionCallbacks permissionCallbacks;
 
 public PermissionPresenter(PermissionAction permissionAction, PermissionCallbacks permissionCallbacks) {
 this.permissionAction = permissionAction;
 this.permissionCallbacks = permissionCallbacks;
 } public void requestReadContactsPermission(int action) {
 checkAndRequestPermission(action, Manifest.permission.READ_CONTACTS);
 }
 
 public void requestReadContactsPermissionAfterRationale(int action) {
 permissionAction.requestPermission(Manifest.permission.READ_CONTACTS, action);
 } private void checkAndRequestPermission(int action, String permission) {
 if (permissionAction.hasSelfPermission(permission)) {
 permissionCallbacks.permissionAccepted(action);
 } else {
 if (permissionAction.shoulShowRequestPermissionRationale(permission)) {
 permissionCallbacks.showRationale(action);
 } else {
 permissionAction.requestPermission(permission, action);
 }
 }
 }

  40. public class PermissionPresenter {
 
 public static final int ACTION_READ_CONTACTS

    = 1;
 public static final int ACTION_SAVE_IMAGE = 2;
 public static final int ACTION_SEND_SMS = 3;
 
 private PermissionAction permissionAction;
 private PermissionCallbacks permissionCallbacks;
 
 public PermissionPresenter(PermissionAction permissionAction, PermissionCallbacks permissionCallbacks) {
 this.permissionAction = permissionAction;
 this.permissionCallbacks = permissionCallbacks;
 } public void requestReadContactsPermission(int action) {
 checkAndRequestPermission(action, Manifest.permission.READ_CONTACTS);
 }
 
 public void requestReadContactsPermissionAfterRationale(int action) {
 permissionAction.requestPermission(Manifest.permission.READ_CONTACTS, action);
 } private void checkAndRequestPermission(int action, String permission) {
 if (permissionAction.hasSelfPermission(permission)) {
 permissionCallbacks.permissionAccepted(action);
 } else {
 if (permissionAction.shoulShowRequestPermissionRationale(permission)) {
 permissionCallbacks.showRationale(action);
 } else {
 permissionAction.requestPermission(permission, action);
 }
 }
 }
 public boolean verifyGrantedPermission(int[] grantResults) {
 for (int result : grantResults) {
 if (result != PackageManager.PERMISSION_GRANTED) {
 return false;
 }
 }
 return true;
 } }
  41. <?xml version="1.0" encoding="utf-8"?>
 
 <RelativeLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_height="match_parent"
 android:layout_width="match_parent"
 android:orientation="vertical">
 


    <include
 android:id="@+id/toolbar"
 layout="@layout/toolbar"/>
 
 <FrameLayout
 android:id="@+id/content"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>
 
 <android.support.design.widget.CoordinatorLayout
 android:id="@+id/main_coordinator_layout"
 android:layout_width="match_parent"
 android:layout_height="match_parent"/>
 
 <ViewStub
 android:id="@+id/permission_rationale_stub"
 android:inflatedId="@+id/permission_rationale"
 style="@style/PermissionRationale"/>
 
 </RelativeLayout> • The components to educate the user are in the layout of the Activity, fragments don’t have to redefine them. • CoordinatorLayout to allow the snackbar being swiped out. • ViewStub for being inflated in runtime. Is used to show the rationale info. Education *CoordinatorLayout bug: https://code.google.com/p/android/issues/detail?id=179600
  42. @RunWith(JUnit4.class)
 public class PermissionPresenterTest {
 PermissionPresenter permissionPresenter;
 @Mock
 PermissionAction permissionAction;


    @Mock
 PermissionCallbacks permissionCallbacks;
 
 @Before
 public void setup() {
 MockitoAnnotations.initMocks(this);
 permissionPresenter = new PermissionPresenter(permissionAction, permissionCallbacks);
 }
  43. @RunWith(JUnit4.class)
 public class PermissionPresenterTest {
 PermissionPresenter permissionPresenter;
 @Mock
 PermissionAction permissionAction;


    @Mock
 PermissionCallbacks permissionCallbacks;
 
 @Before
 public void setup() {
 MockitoAnnotations.initMocks(this);
 permissionPresenter = new PermissionPresenter(permissionAction, permissionCallbacks);
 } @Test
 public void shouldCallAcceptedPermission() { when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(true);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionCallbacks).permissionAccepted(ACTION_READ_CONTACTS);
 }
  44. @RunWith(JUnit4.class)
 public class PermissionPresenterTest {
 PermissionPresenter permissionPresenter;
 @Mock
 PermissionAction permissionAction;


    @Mock
 PermissionCallbacks permissionCallbacks;
 
 @Before
 public void setup() {
 MockitoAnnotations.initMocks(this);
 permissionPresenter = new PermissionPresenter(permissionAction, permissionCallbacks);
 } @Test
 public void shouldCallAcceptedPermission() { when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(true);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionCallbacks).permissionAccepted(ACTION_READ_CONTACTS);
 } @Test
 public void shouldCallShowRationale() {
 when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(false);
 when(permissionAction.shoulShowRequestPermissionRationale(READ_CONTACTS)).thenReturn(true);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionCallbacks).showRationale(ACTION_READ_CONTACTS);
 }
  45. @RunWith(JUnit4.class)
 public class PermissionPresenterTest {
 PermissionPresenter permissionPresenter;
 @Mock
 PermissionAction permissionAction;


    @Mock
 PermissionCallbacks permissionCallbacks;
 
 @Before
 public void setup() {
 MockitoAnnotations.initMocks(this);
 permissionPresenter = new PermissionPresenter(permissionAction, permissionCallbacks);
 } @Test
 public void shouldCallAcceptedPermission() { when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(true);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionCallbacks).permissionAccepted(ACTION_READ_CONTACTS);
 } @Test
 public void shouldCallShowRationale() {
 when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(false);
 when(permissionAction.shoulShowRequestPermissionRationale(READ_CONTACTS)).thenReturn(true);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionCallbacks).showRationale(ACTION_READ_CONTACTS);
 } @Test
 public void shouldRequestPermission() {
 when(permissionAction.hasSelfPermission(READ_CONTACTS)).thenReturn(false);
 when(permissionAction.shoulShowRequestPermissionRationale(READ_CONTACTS)).thenReturn(false);
 
 permissionPresenter.requestReadContactsPermission(ACTION_READ_CONTACTS);
 verify(permissionAction).requestPermission(READ_CONTACTS, ACTION_READ_CONTACTS);
 }
 }
  46. Useful docs and acknowledgements • New permissions model. • Android

    Patterns Permissions. • Exploring the new android permissions model (Joe Birch). • Everything every Android de must know about new Android’s runtime permission (nuuneoi). • The Clean Architecture (Uncle Bob). • Architecting Android… The clean way? (Fernando Cejas). • MVP for Android: how to organise the presentation layer (Antonio Leiva). • Effective Android UI (Pedro V. Gómez Sánchez). • Engineering Wunderlist for Android (César Valiente & Wunderlist Android team ). And many thanks as well to Fine Cinnamon (OJTHandler) for all the knowledge and good people that can be found there (and funny times too).
  47. License (cc) 2015 César Valiente. Some rights reserved. This document

    is distributed under the Creative Commons Attribution-ShareAlike 3.0 license, available in http:// creativecommons.org/licenses/by-sa/3.0/ All device screenshots used in the “education” section, belong to Google Inc. and can be found here.