Overview

Skill Level: Intermediate

One of our recommended practices is to set up a way to obtain SDK debug information from your app to help when getting technical support. Specifically, it’s very common for our support team to request mobile user ID and SDK versions, though there are a number of other items you may want to include in a debug screen such as OS version and platform, build version of your app, channel ID, app key and so on. If you have this information easily available from devices in the field, it becomes significantly easier to resolve problems. Most customers prefer to keep this information hidden until it’s needed. We recommend that as well. Thus the question arises: how can one make a secret trigger that can be used to report SDK information, but make it relatively easy to access when needed. This is one possible solution. You may want to use a different way to trigger the way to open the screen, and consider issues with accessibility.

Prerequisites

Not what you’re looking for? Check out all our available tutorials for mobile app messaging here.

Step-by-step

  1. Setting up a secret trigger on Android to display SDK debug information

    Changes to the sample app
    We will make these changes to the sample app. Changes to your app will be similar.

    Decide where you’ll trigger from
    You will need to find an appropriate place in your app from which a user invokes the trigger. Often this is the configuration screen, but if you have a lot of elements on that screen, another view may be preferable. It’s best if it’s a screen where you’re not already collecting touch events.

    A view which has a text entry field offers slightly more security than one which doesn’t. We will describe why further on.

    In our case, we will be adding the event to the “Send User Attributes” screen in the sample app.

  2. Add the changes

    To AttributeSampleActivity, add the following variables:

    // Secret diagnostics
    // Variables required
    // Change pattern to indicate the sequence of touches required to activate
    // the secret action.
    private int[] pattern = {1, 2, 3, 4, 3, 2, 3, 2};
    private HashSet pointers = new HashSet();
    private int patternIndex = 0;
    

     

    The pattern instance variable represents the number of touches on the screen that must occur for the event to be triggered. In this case, we require detection of one finger touching the screen, then two, then three, then four. After that one finger must be lifted (leaving three) and then another finger must be lifted (leaving two). Next an additional finger must touch the screen (for a total of three). Finally, one finger must be lifted, leaving two. When that happens, the event will be triggered. Any deviation from the pattern resets it back to the beginning.

    We strongly recommend you change the sample pattern. You might want to use a different pattern for each app, or even each major revision of the app. Remember, however, that your support reps will need to be able to tell people how to trigger the diagnostic event. If it’s too complicated  (or if some of your devices don’t support detection of four touches on a screen) you may need to adjust it.

  3. Add these methods

    Add the following three methods to the bottom of the Activity (AttributeSampleActivity in the sample):

    // Secret diagnostics
    // We override dispatchTouchEvent and count the number of fingers down
    // based on the values in pattern. See:
    // https://stackoverflow.com/questions/48441348/whats-the-best-way-to-implement-hidden-debug-options
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch(ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                // new gesture, reset
                pointers.clear();
                patternIndex = 0;
                pointers.add(Integer.valueOf(ev.getPointerId(ev.getActionIndex())));
                checkPatternAndOpen();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                // new touch
                pointers.add(Integer.valueOf(ev.getPointerId(ev.getActionIndex())));
                checkPatternAndOpen();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                // touch released
                pointers.remove(Integer.valueOf(ev.getPointerId(ev.getActionIndex())));
                checkPatternAndOpen();
        }
        return super.dispatchTouchEvent(ev);
    }
    
    private void checkPatternAndOpen() {
        if (pattern[patternIndex] == pointers.size()) {
            // progressing
            patternIndex++;
            if (patternIndex == pattern.length) {
                // triggered, show debug screen
                patternIndex = 0;
                showDebugScreen();
            }
        } else {
            // reset
            patternIndex = 0;
        }
    }
    
    private void showDebugScreen() {
        // This requires that a field be the correct value. If it's not,
        // nothing will happen - even if the user enters the correct
        // sequence of touches. Here I'm just reusing an existing string in the
        // class and testing with .equals, but you can do your own calculations
        // and obscure them as much as you want.
        if(!attributeKey.getValue().equals("AttributesActivity")) {
            return;
        }
        // Open your diagnostic screen here. Don't forget to include
        // app key, mobile user ID and channel!
        // See https://developer.ibm.com/customer-engagement/docs/watson-marketing/ibm-engage-2/mobile-push/mobile-push-android/accessing-registration-details/
        // If you make it easy for the user to email those details to you,
        // your problem is likely to be resolved more quickly.
        RegistrationDetails registrationDetails = MceSdk.getRegistrationClient().getRegistrationDetails(getApplicationContext());
        if (registrationDetails.getChannelId() == null || registrationDetails.getChannelId().length() == 0) {
            Toast.makeText(AttributesSampleActivity.this, resourcesHelper.getString("no_sdk_reg_toast"), Toast.LENGTH_SHORT).show();
        } else {
            Intent intent = new Intent();
            intent.setClass(getApplicationContext(), RegistrationDetailsSampleActivity.class);
            startActivity(intent);
        }
    }
    

     

    The action that is executed when the event is triggered is in showDebugScreen. In the case of the sample app, it already has a screen which reports mobile user ID, channel ID and app key. If the touch events are appropriately triggered, we invoke it.

    Note the test for attributeKey in showDebugScreen. In our example, the sample app will never invoke an action (even if the trigger touches happen) unless the “key” field contains the value “AttributesActivity”. We chose that value because it’s already present in the class, so it will be reused from the class pool. Doing so obscures the code slightly from those who decompile the class. Similarly, you might want to change method names so people who decompile will not think to look further, or change how the pattern is stored (perhaps by encoding it or constructing it via computation). Ultimately, because it’s Java bytecode in an APK, your code may be extracted by anyone who installs your app. Because of this, keep passwords out of your diagnostics.

    Another option (rather than opening a screen) is to create an email message addressed to your support group. In general, this is done by creating an Intent:

    https://stackoverflow.com/questions/2197741/how-can-i-send-emails-from-my-android-application

     

Expected outcome

Need more help? Check out all of our available tutorials for mobile app messaging here.

Join The Discussion

Your email address will not be published. Required fields are marked *