Slide 1

Slide 1 text

New Permissions model on a “Nice” way César Valiente

Slide 2

Slide 2 text

Who is this guy? Image Placeholder César Valiente Android Engineer @Wunderlist (@Microsoft) Android Google Developer Expert (GDE) +CesarValiente @CesarValiente

Slide 3

Slide 3 text

What do we currently have?

Slide 4

Slide 4 text

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.

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

Permission groups CALENDAR CONTACTS MICROPHONE SENSORS CAMERA LOCATION PHONE SMS STORAGE

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Permissions on runtime Just in Android 6.0

Slide 9

Slide 9 text

Permissions on settings All API versions!

Slide 10

Slide 10 text

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…

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

User education Educate up-front Ask up-front Educate in context Ask in context Critical Clear Secondary Unclear

Slide 13

Slide 13 text

User education (2) Educate before asking Ask up-front Ask in context

Slide 14

Slide 14 text

User education (3) Educate in context Provide an immediate benefit Only ask for relevant permissions

Slide 15

Slide 15 text

User education (4) Feedback when permission is denied Critical permissions

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Permission flow

Slide 20

Slide 20 text

User wants to do an action that involves a permission

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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)

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

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)

Slide 35

Slide 35 text

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?

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

Demo time

Slide 39

Slide 39 text

Concern separation App (Android project) Domain (Java project) Actions (Android project) Use cases Action

Slide 40

Slide 40 text

Model View Presenter (MVP) View Presenter Model App (presentation)

Slide 41

Slide 41 text

Action PermissionAction (Interface) ActivityPermission ActionImpl SupportPermission ActionImpl Permission Actions

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

App (presentation) Interaction BaseActivity MainActivity ContactList Fragment Contact Fragment PermissionPresenter PermissionCallbacks ContactPresenter ContactListPresenter MainView

Slide 46

Slide 46 text

Interaction App (presentation) BaseActivity MainActivity ContactList Fragment Contact Fragment Action XXXPermissionAction Impl PermissionPresenter PermissionCallbacks ContactPresenter ContactListPresenter MainView

Slide 47

Slide 47 text

Implementation

Slide 48

Slide 48 text

public interface MainView {
 //Rest of the methods have been removed here
 
 void requestReadContacts();
 
 void requestSaveImage();
 
 void requestSendSMS(); }

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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


Slide 56

Slide 56 text

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

Slide 57

Slide 57 text


 
 
 
 
 
 
 
 
 
 
 
 • 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

Slide 58

Slide 58 text

Testing? easy peasy

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

? +CesarValiente @CesarValiente

Slide 65

Slide 65 text

+CesarValiente @CesarValiente Thanks!

Slide 66

Slide 66 text

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.