Simply Patrick

Better Way to Request Runtime Permissions

If you are an Android developer, you definitely know an important change beginning in Android 6.0 (API 23) is run time permission: users grant permissions to apps while the app is running, not when they install the app. This official training guide explains clearly how you can check for permission and request them at runtime.

Let me list the sample code in the training guide so that you can understand what improvements I would like to do with them:

Check for Permissions

// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS)
        != PackageManager.PERMISSION_GRANTED) {
    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
            Manifest.permission.READ_CONTACTS)) {
        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.
    } else {
        // No explanation needed, we can request the permission.
        ActivityCompat.requestPermissions(thisActivity,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    }
} else {
  // permission was granted, yay! Do the task you need to do.
}

Request Permissions

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted, yay! Do the task you need to do.
            } else {
                // permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
            return;
        }
    }
}

The sample code looks nice but it does not work so well in real-world projects for several reasons:

  • The sample structure encourages you to write duplicated code:
    • There are two places your app have to perform the real task:
      • while permission is already granted - line 17
      • while permission is granted after user interaction - line 8
    • There are two places your app have to request the permission:
      • after user see the explanation and decide to request permission again - line 7
      • while no explanation is needed - line 12
  • When permission is finally granted from user, the original context has lost. For example, when my app tries to make a phone call, the context is the phone number. A typical solution is to store the context in class field, such as a mPhoneNumberToCall. It is bad and something I would like to avoid.
  • The sample code does not suggest how to show an explanation asynchronously.

More Elegant Solution

My basic idea is using Runnable (with help of Java8 lambda) to capture the context and prevent duplication. Here is my way to :

  • The original task is wrapped as a Runnable that can be stored in HashMap and retrieved later for execution without repeating its logic. All necessary information can be captured when the lambda is created.
  • The runnable task is wrapped again by a permission checker which is also an Runnable.
  • Use Snackbar to show an explanation

Here is revised version of original sample code:

private HashMap<String, Runnable> mPendingPermissionRequests = new HashMap<>();
private static final int PENDING_PERMISSION_REQUESTS = 0;

private Runnable newPermissionRequester(Activity activity, String permission, String explanation, Runnable task) {
    return () -> {
        if (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
            task.run();
            return;
        }

        Runnable requestPermission = () -> {
            ActivityCompat.requestPermissions(getActivity(), new String[] {permission}, PENDING_PERMISSION_REQUESTS);
            mPendingPermissionRequests.put(permission, task);
        };

        // Should we show an explanation?
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
            // Show an explanation to the user *asynchronously* -- don't block this thread waiting for the user's
            // response! After the user sees the explanation, try again to request the permission.
            Snackbar.make(getView(), explanation, Snackbar.LENGTH_LONG)
                    .setAction(R.string.request, v -> {
                        requestPermission.run();
                    })
                    .show();
        } else {
            // No explanation needed, we can request the permission.
            requestPermission.run();
        }
    };
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == PENDING_PERMISSION_REQUESTS) {
        for (int i = 0; i < permissions.length; i++) {
            String permission = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_GRANTED && mPendingPermissionRequests.containsKey(permission)) {
                // permission was granted, yay! Do the task you need to do.
                mPendingPermissionRequests.remove(permission).run();
            }
        }
    }
}

Here is an example to make a phone call:

newPermissionRequester(
    getActivity(),
    Manifest.permission.CALL_PHONE,             // permission to request
    getString(R.string.call_phone_permission),  // explanation to user
    () -> startActivity(new Intent(Intent.ACTION_CALL, Uri.fromParts("tel", phone.number, null))))
.run();

Of course you can try extracting a more reusable PermissionRequester class but this is good enough for me.


The Elements of Good Commit Messages

通常從 commit messages 就可以看出一個軟體開發團隊是否有紀律、注重品質、並且彼此溝通良好。

如何寫好 commit message

這是我去年在公司內部做的一個 presentation,目的是讓大家知道要在 commit message 寫那些東西:

負面範例

請至少避免寫這些沒有意義的 commit message


Building AOSP on MAC

Although I am familiar with building AOSP source, I still learn something from the following blog post series by Udi Cohen in 2014:

The last time I built AOSP on Mac, there were still some pitfalls. I finally gave up and settled on doing it in Ubuntu. After reading Udi’s posts, I decided to give it a try again. Another motivation is to experiment some new changes that will be available in upcoming Android N:

  • Soong build system
  • Jack/Jill for Java code
  • Clang for C/C++ code

zsh or bash?

zsh with Oh My Zsh is my default shell but I still prefer bash to prevent possible compatibility issues when building AOSP.

$ exec bash -l

exec will replace current shell with bash and -l is necessary for ~/.bash_profile to be executed.

$ export JAVA_HOME=$(/usr/libexec/java_home)

full_eng or full_x86_64-eng?

QEMU-based ARM emulation is slow compared with HAXM-accelerated x86-64 emulation. You should definitely go for x86_64 target if ARM emulation is not important to you.

OS X specific

There are still two tweaks you should do before building AOSP code on Mac:

Java

You need to set JAVA_HOME variable explicitly to prevent build error of “could not find jdk tools.jar”:

curl

AOSP does not like SecureTransport-based curl for unknown reason. You have to build an OpenSSL-based curl and manipulating $PATH to force its use:

$ brew install curl --with-openssl
$ export PATH=$(brew --prefix curl)/bin:$PATH

IntellJ or Android Studio?

I feel more comfortable using IntelliJ to analyze AOSP codebase and debug framework code. Android Studio is more app-oriented and sometimes does not work as expected when abused with this hack.


Reactor Pattern