Wednesday, October 28, 2015

Tips & Tricks: Dangerous Permissions, Part 2

This entry is going to focus on what happens when a User revokes a permission while the app is backgrounded.  

The user can change a permission status at anytime by going to the settings app=>apps=>pick any app=>Permissions.

If the user grants a permission this way then nothing special happens.  Your app (if it is up) just keeps chugging along.

The real interesting stuff happens when the user revokes a permission for your app.  If the app isn’t running, then nothing happens.  If the app is running, then it will fully re-create.  This means that the old application is destroyed.  It will take down all of its Services and all static data is lost.  It will then start a new version of the app.   This will call the Application objects onCreate.  

The really fun problem comes in when the user foregrounds your app again.  The system will restore to its last known point using any saved bundles.

This can leave your app in a weird state: where it has restarted, but also restored to a state without any breadcrumbs.  As one of my coworkers put it: “It’s like a game where you dies and regenerate right in front of the Boss’s door without any of the goodies you built up along the way”.  Pretty apt way to describe it.

For most apps, this isn’t a big deal because they automatically create the database, etc. as part of app start.  However, it is important to understand to avoid painting yourself into a corner data wise.

I included an demonstration of this in my Example using counters.  I maintain a static counter and a local counter.

   1:  
   2:     /**
   3:      * The static counter.  This is here to show that static data resets when revoking a permission
   4:      * via the settings app.
   5:      */
   6:     private static int sStaticCounter;
   7:  
   8:  
   9:     /**
  10:      * a local counter.  This is here to show that activity restores when revoking a permission via
  11:      * the settings app.
  12:      */
  13:     private int mCounter;
  14:  

  Each time the Toolbar’s “Click Me” is clicked I increment both counters.



   1:  
   2:  
   3:     @Override
   4:     public boolean onOptionsItemSelected(MenuItem item) {
   5:         int id = item.getItemId();
   6:  
   7:         //noinspection SimplifiableIfStatement
   8:         if (id == R.id.action_settings) {
   9:             ++mCounter;
  10:             ++sStaticCounter;
  11:  
  12:             updateCounterUi();
  13:             requestPermissions();
  14:             return true;
  15:         }
  16:  
  17:         return super.onOptionsItemSelected(item);
  18:     }

The value of the counters is shown in the UI:


device-2015-10-10-211138


The local counter value is saved into save state bundle in onSaveInstance().  This value is then restored in the activity’s onCreate method.


So here is the recreation:



  • Start the app, notice that both counters are 0.

start



  • Click the “Click Me”, notice that both counters are 1.

device-2015-10-10-211138



  • Click the “Click Me” in the Snackbar. 
  • Grant the permission.
  • Background the app
  • Start the Settings=> App=> PermissionsExample=>Permissions.
  • Notice that the “Contacts” permission is granted
  • Turn off the “Contacts” permission.

    • At this point, the app recreates

  • Foreground the PermissionsExample app.  Notice that the static data is reset, but the data from the savedInstance bundle restored correctly.

device-2015-10-10-212749


A word of Advice


Regardless of whether a permission is not dangerous, I always check the permission status under the app settings.  This is because some normal permissions are grouped under an umbrella permission that can be revoked.   If any permissions shows up then you have to be prepared for the above scenario to occur. 


For instance in the example, I am using the GET_ACCOUNTS permissions which is technically normal*.  However, it is grouped under the “Contacts” group, which can be revoked. That means that I have to deal with the above situations even though the specific permission I am using cannot be revoked.


*There is an issue reported to Google to make GET_ACCOUNTS dangerous.  As of this writing the change is implemented, but not delivered.


Example


The example can be found in my GitHub PermissionsExample repository.

Wednesday, October 21, 2015

Tips & Tricks: Dangerous Permissions, Part 1

The biggest new feature of Marshmallow is the permissions model change.  A permission is now either dangerous or normal.  Normal permissions behave just like the old permissions.  They are automatically granted at app download and cannot revoked by the User.

Dangerous permissions are the really interesting ones because the user must explicitly grant the app that permission at run-time.   The user can also revoke the permission anytime via the device settings app.

Even if the permission is normal, it can be classed into a grouping that is revocable.  This doesn’t mean that the user can deny your specific permission, but it will cause your app to recreate if the user revokes it in the background.

Basics

If you don’t have permission for a feature, then attempting to use that feature will cause an exception.  To avoid that you need to do the following each time you want to use the feature protected by the permission:

Permission Model - New Page

Obviously it look a little different in code so here is my step-by-step implementation.

Checking Permissions:

This all done in a custom requestPermissions method of the MainActiviity:

   1: private void requestPermissions() {
   2:  
   3:         //use the static ActivityCompat methods if the minimum app level is less than 23.
   4:         if (ActivityCompat.checkSelfPermission(this, permission.GET_ACCOUNTS) == PackageManager.PERMISSION_DENIED) {
   5:  
   6:             //The permission is denied, so ask the OS if we should show a rationale that explains
   7:             //why the permission is needed.
   8:  
   9:             if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission.GET_ACCOUNTS)) {
  10:                 showRationale();
  11:             }
  12:             else {
  13:                 requestAccountsPermission();
  14:             }
  15:         }
  16:         else {
  17:             Toast.makeText(this, "Activity: Permission Already Granted!", Toast.LENGTH_LONG).show();
  18:         }
  19:     }

This method uses the ActivityCompat static methods to check the permissions.  Using those methods eliminates the pesky logic breaks for handling Marshmallow vs pre-Marshmallow devices. 


Basically,  it calls checkSelfPermission to determine if the desired permission is granted.  If it is then is just shows a toast.  If then permission is denied, then it needs to determine whether to show a rationale for the permission to the user.  This is done via the shouldShowRequestPermissionRationale method.  This will normally return true only if the user has denied the permission before.  You are not obligated to show the rationale, but it is a really good idea to give the user an explanation for why they should grant the permission.


How and what to show in the rationale is completely up to you.  In the example, I use a Snackbar.  When the user clicks on the Snackbar’s “Click Me” I request the OS to display the Permission prompt.  This is done in the requestAccountsPermission method.



   1: private void requestAccountsPermission() {
   2:      //use the static ActivityCompat methods if the minimum app level is less than 23.
   3:      ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, PERMISSION_REQUEST_CODE);
   4:  }

This method calls the ActivityCompat’s requestPermissions method.  This method takes a string array of the permissions to request and a code to identify the request.



   1:  
   2:     private static final int PERMISSION_REQUEST_CODE = 1;
   3:  
   4:     private static final String PERMISSIONS[] = {permission.GET_ACCOUNTS};

Since the user prompt for the permissions is handled by the OS, the activity will get the response when its onRequestPermissionsResult method is called.



   1:  
   2:     @Override
   3:     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
   4:         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
   5:  
   6:         if (requestCode == PERMISSION_REQUEST_CODE) {
   7:             if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
   8:                 Toast.makeText(this, "Activity: Permission Granted!", Toast.LENGTH_LONG).show();
   9:             }
  10:             else {
  11:                 Toast.makeText(this, "Activity: Permission Denied!", Toast.LENGTH_LONG).show();
  12:             }
  13:         }
  14:  
  15:     }

From here you can proceed with the feature if the permission is granted or discontinue if the permission is still denied.


Fragment Quirk


Fragments can follow the same process for requesting permissions, but be careful cannot using ActivityCompat methods. If the ActivityCompat requestPermissions method is called, then the Fragments onRequestPermissionsResult will not be called. Ever.   Instead,  use the built-in requestPermissions method only.


Example


The example can be found in my GitHub PermissionsExample repository.

Wednesday, October 14, 2015

MyNotes: Marshmallow

 

I really didn’t have to do much for marshmallow. I did notice that I was using the WRITE_EXTERNAL_STORAGE permission unecessarily so I removed it.   Then, I up-leved my gradle files and ran the app on marshmallow.  

Since I don’t have any permissions, I don’t have to worry about the on-demand permission issues. You have no idea how happy I am about that because it can be a real PIA to retrofit an app for them. I did create an example project to demonstrate how to deal with permissions, but that is a different post.

That leave only  Doze mode and the Auto Backup to deal with.  Neither is a big deal.  I tested the Auto Backup with the instructions found here.  Testing Doze Mode was a little tougher because the instructions didn’t really work.   I included my steps below.

In either case, my app tested without any glitches so I am claiming victory for Marshmallow.

Detour: Testing Doze Mode

The documentation for testing doze mode is a little sparse, but here is the procedure I used to test it.

Enabling Doze Mode:

  1. Start an “M” emulator
  2. Install your app on the device
  3. Start a terminal
    1. adb shell dumpys deviceidle enable
    2. adb shell dumpsys battery unplug
      • (emulator screen turns off)
    3. adb shell dumpsys deviceidle step
      • output is IDLE_PENDING
    4. adb shell dumpsys deviceidle step
      • output is SENSING
    5. adb shell dumpsys deviceidle step
      • output is IDLE
    6. adb shell dumpsys deviceidle step
      • output is IDLE_MAINTENANCE

Disabling Dose Mode:

  1. In a terminal:
    1. adb shell dumpys deviceidle disable
    2. adb shell dumpsys battery ac
  2. Go to the emulator and press escape
    • The screen turns back on

Commits

The changes can be found at https://github.com/fsk-software/mynotes/commit/75bd19218d46e23066e067b9506895ab9a56359f.

Wednesday, October 7, 2015

MyNotes: Goodbye Guava

 

I have noticed a few odd build failures lately that were not due to syntax errors.  These were the java.exe failing with error code 2.  This is a big red flag that there is too  much going on library wise.   The root of the problem is that android has issues when an app has more than 65k methods.  This includes all of the libraries that get built into the app.

Guava is a huge monolithic library.  And I brought it into both my common library module and the mynotes module.  In addition, it looks like ButterKnife also brings in a Guava version.  That would be three compilations of a very large library for  very small app.

I tried doing some selective behavior with gradle to minimize the library impact, but none of them work really well and made the files very messy.  I also thought about enabling multidexing, which would also fix the problem, but that seemed ridiculous to do for a tiny app.

So that left me with analyzing whether I really needed Guava.  The short answer is no.  I am only using two classes: Preconditions and Strings.  And I wasn’t even using the majority of the methods in those classes.

As a result, I took out Guava and created my own Preconditions and Strings classes.  I kept the method and class names the same, but spun my own implementations.  I stuck those classes in the Common Library for now.

Ta Da:  weird build problems magically go away.  The moral of the story: be careful bringing in libraries unless you really need them.

Commits

The changes can be found at https://github.com/fsk-software/mynotes/commit/75bd19218d46e23066e067b9506895ab9a56359f.