Android Marshmallow — Permission Model and Authentication 1



Android MarshmallowAndroid development is always evolving and with each change comes a list of features for developers to utilize. The changes are not always so easy to adopt though because it requires adopting new standards. For example having your Android app support Lollipop is vastly different from Marshmallow.

UPDATE: Part 2

Overview

I am going to go over some of the new features to Android Marshmallow to help you make your application compatible and to utilize some of its new features. I am going to cover the new templates from Android Studio, new API and changes, new permissions model, and authentication with device credentials and fingerprints. Do keep in mind most of what I will talk about requires Android SDK 23 or above.

Templates

With Android Studio 1.5+ more templates are introduced to help you start your app. There is the Empty Activity template, which is just the same as what older version of Android studio provides. It just contains the main activity java file and activity main XML file.

There another template call Blank Activity and this one has two options. One is activity based and the other is fragment based. You should favor using fragment over activity when you need to account for multiple screen sizes and UI.

New API and changes

  • Direct sharing  – is now easier to do. You can invoke certain activity from your app directly.
  • Adoptable storage – You can add in storage and make it part of the system internal storage. There are also new API function calls that help you handle the paths. This removes the need for you to deal with absolute paths.
  • Improved performance for audio, video, and camera.

Permissions Model

Before Marshmallow, permissions had to be granted all at installation. If upgrades require new permissions the user must grant permission before the upgrade can happen. This old permission model was more cumbersome for the user.

The new permissions model present in Marshmallow has the user grant permission at runtime. Not only does this model offer the user an easy time to update their apps, it also prevents unnecessary permissions to be granted until needed.

Permissions are divided into two categories: normal and dangerous. Normal permissions are granted automatically. A permission is normal if it does not threaten privacy or other security considerations. Dangerous permissions require explicit permission from the user.

Permissions are granted in groups instead of individually. However, the permissions still must be declared individually in the Android Manifest. During runtime, permissions request are for an entire group. For example, in your app, you have permissions CALL_PHONE and READ_PHONE_STATE, when permission request occurs it will be for the group PHONE. This will grant permission for both CALL_PHONE and READ_PHONE_STATE.

Here is a small code example on how to apply the runtime permission (syntax might be different when you see this):

private void callForInfo() {
    Intent phone_call_intent = new Intent(Intent.ACTION_CALL);
    phone_call_intent.setData(Uri.parse("tel:543-347-2343"));
    startActivity(phone_call_intent);
}
...
FloatingActionButton call_button = (FloatingActionButton) findViewById(R.id.call_button);
call_button.setOnClickListener(new View.OnClickListener) {
    @Override
    public void onClick(View view) {
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)
            != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.CALL_PHONE)) {
                Toast.makeText(this, "Asking for permission again", Toast.LENGHT_SHORT).show();
            }
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE},
                    MY_PERMISSIONS_REQUEST_CALL_PHONE);
            return;
        }
        callForInfo();
    }
});

@Override
public void onRequestPermissionsResult(int request_code, @NonNull String[] permissions, @NonNull int[] grant_results) {
    switch(request_code) {
        case MY_PERMISSIONS_REQUEST_CALL_PHONE:
            if (grant_results.length > 0 &&
                   grant_results[0] == PackageManger.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "Permission granted!", Toast.LENGTH_SHORT).show();
                callForInfo();
            }
            else {
                Toast.makeText(MainActivity.this, "Permission denied!", Toast.LENGTH_SHORT).show();
            }
            break;
    }
}

Authenticate with device credentials

With device credentials authentication, you can ask the user to provide authentication before an action. The type of authentication is dependent on what the user sets. For this feature to be usable, the user must have authentication setup and be running a device with API 23 or above.

Here is some sample code to use device credentials authentication

private static final String KEY_NAME = "my_key";
private static final byte[] SECRET_BYTE_ARRAY = new byte[]{1, 2, 3, 4, 5, 6};
private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 42;
private static final int AUTHENTICATION_DURATION_SECONDS = 5;
private KeyguardManager mKeyguardManager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstancedState);
    ...
    mKeyguardManger = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    createKey();
}

/**
 * Creates a symmetric key in the Android Key Store which can only be used after the user has
 * authenticated with device credentials within the last X seconds.
 */
 private void createKey() {
     // Generate a key to decrypt payment credentials, tokens, etc.
     // This will most likely be a registration step for the user when they are setting up your app.
     try {
         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
         keyStore.load(null);
         KeyGenerator keyGenerator = KeyGenerator.getInstance(
             KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");

         // Set the alias of the entry in Android KeyStore where the key will appear
         // and the constrains (purposes) in the constructor of the Builder
         keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
             KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
             .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
             .setUserAuthenticationRequired(true)
             // Require that the user has unlocked in the last 30 seconds
             .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
             .build());
         keyGenerator.generateKey();
     } catch (NoSuchAlgorithmException | NoSuchProviderException
             | InvalidAlgorithmParameterException | KeyStoreException
             | CertificateException | IOException e) {
         throw new RuntimeException("Failed to create a symmetric key", e);
     }
}

/**
 * Tries to encrypt some data with the generated key in {@link #createKey} which is
 * only works if the user has just authenticated via device credentials.
 */
private boolean tryEncrypt() {
    try {
        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
        Cipher cipher = Cipher.getInstance(
                KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
                + KeyProperties.ENCRYPTION_PADDING_PKCS7);

        // Try encrypting something, it will only work if the user authenticated within
        // the last AUTHENTICATION_DURATION_SECONDS seconds.
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        cipher.doFinal(SECRET_BYTE_ARRAY);

        // If the user has recently authenticated, you will reach here.
        return true;
    } catch (UserNotAuthenticatedException e) {
          // User is not authenticated, let's authenticate with device credentials.
          showAuthenticationScreen();
          return false;
    } catch (KeyPermanentlyInvalidatedException e) {
          // This happens if the lock screen has been disabled or reset after the key was
          // generated after the key was generated.
          Toast.makeText(this, "Keys are invalidated after created. Retry the purchase\n"
                         + e.getMessage(),
                         Toast.LENGTH_LONG).show();
          return false;
    } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException
            | CertificateException | UnrecoverableKeyException | IOException
            | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
          throw new RuntimeException(e);
    }
}

private void showAuthenticationScreen() {
    // Create the Confirm Credentials screen. You can customize the title and description. Or
    // we will provide a generic one for you if you leave it null
    Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
    if (intent != null) {
        startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS:
            // Challenge completed, proceed with using cipher
            if (resultCode == RESULT_OK) {
                if (tryEncrypt()) {
                   Toast.makeText(this, "tryEncrypt Success", Toast.LENGTH_SHORT).show();
                } else {
                   Toast.makeText(this, "tryEncrypt Failed", Toast.LENGTH_SHORT).show();
                }
            } else {
                 Toast.makeText(this, "tryEncrypt Failed", Toast.LENGTH_SHORT).show();
            }
            break;
     }
}

This sample code provides the base for handling devices credentials. KeyguardManager is utilized to manage the device credentials authentication process. We create and manage an AndroidKeyStore for the encryption process.

Authentication with fingerprints

For fingerprints support you need to be running on a device with API23 or above. Unlike other APIs, for fingerprints authentication, you must implement your own UI to prompt for a fingerprint. The permission USE_FINGERPRINT must also be declared in the Manifest file. Google has provided a thorough sample code of working with fingerprint authentication. The sample project is call “Fingerprint Dialog” and you can access it via Android Studio’s import sample function or the GitHub page.

To test or debug fingerprint authentication, you must have either a physical device with fingerprint scanning function or the Android Debug Bridge (ADB). If you do not have a physical device, you can still test fingerprint authentication through ADB and the Android emulator that comes with Android Studio.

Here is how you can emulate fingerprint for the Android emulator:

  1. In your app running on the emulator get to where you are prompted for fingerprint input
  2. Open up a terminal (Linux or Mac) or command prompt (Windows)
  3. Navigate to adb (skip if you have adb in your PATH)
  4. Invoke fingerprint emulation with the following command line:
    adb -e emu finger touch 1

    Note: emu is the name of your emulator. Also, try to avoid connecting another Android device to your computer when doing this. Otherwise, you will need to let ADB know which device id to send the emulated finger touch to.

Android Marshmallow brings to the table some major changes. Knowing how to work with these new changes can help you develop better apps and make development easier. You can deliver a better user experience by leveraging the new features.

If you found this helpful, share it with someone else who can benefit from this. Also, follow me on twitter for more similar content.

UPDATE: Part 2


About Steven To

Steven To is a software developer that specializes in mobile development with a background in computer engineering. Beyond his passion for software development, he also has an interest in Virtual Reality, Augmented Reality, Artificial Intelligence, Personal Development, and Personal Finance. If he is not writing software, then he is out learning something new.

One thought on “Android Marshmallow — Permission Model and Authentication

Comments are closed.