HowTo: Create a customized "Remote Disable" for Native Android and iOS applications

Overview

One of the features of the IBM MobileFirst Platform is to prevent certain apps from connecting to the server. This is very useful when a vulnerability is found in one of the versions of your app and you want to prevent this app from accessing the server or using server resources.

Default Behavior

The default, out-of-the box behavior for Remote Disable is to show an "Access Denied" popup with an "OK" and a "Download" button for the new app. This popup is triggered every time a request to the IBM MobileFirst Platform Server occurs. Essentially, by default your app is turned into an offline only app. Everything that does not perform a request to the MobileFirst server still works normally. What about changing the overall behavior of the app when it is disabled? In the next section we will go over examples on how to do just that for Android and iOS apps.

Custom Implementation

By now you may ask yourself: How do I change the default behavior of Remote Disable? The simple answer: You have to register an instance of WLChallengeHandler to the WLClient instance for the following realm "wl_remoteDisableRealm". Below, you will see example of how to implement this for Android and iOS.

Android

First, you will need a native Android application with the IBM MobileFirst SDK included in it; for more information on how to do this visit the Getting Started section for Android. Next, let's create the challenge handler and response listener, MyRemoteDisableChallengeHandler and MyResponseListener respectively. MyRemoteDisableChallengeHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.ibm.mobilefirst.android.challengehandler;
import com.ibm.mobilefirst.android.MainActivity;
import com.worklight.wlclient.api.challengehandler.WLChallengeHandler;
import org.json.JSONObject;
/**
 * Custom Challenge Handler for RemoteDisable
 */
public class MyRemoteDisableChallengeHandler extends WLChallengeHandler {
    private MainActivity mainActivity;
    public MyRemoteDisableChallengeHandler(final MainActivity activity) {
        super("wl_remoteDisableRealm");
        mainActivity = activity;
    }
    @Override
    public void handleSuccess(JSONObject jsonObject) {
        // the app is not notifying nor disabled. normal process can follow
    }
    @Override
    public void handleFailure(JSONObject jsonObject) {
        // create an enum from the Notification for easier handling
        final DisableMessage disableMessage = DisableMessage.fromJSON(jsonObject);
        /*
         * the app is disabled. disabled the connect button in the main activity
         * you could do other things like changing the colors and show a different activity
         */
        mainActivity.disableApp(disableMessage);
    }
    @Override
    public void handleChallenge(JSONObject jsonObject) {
        // create an enum from the Notification for easier handling
        final DisableMessage disableMessage = DisableMessage.fromJSON(jsonObject);
        /*
         * the app is not disabled but it is marked as "Active, Notifying" submit challenge answer
         * so that the normal flow of the app continues.
         *
         * NOTE: if you also want to disable the app when it's notifying then don't call
         * submitChallengeAnswer and follow a similar approach to the handleFailure method
         */
        submitChallengeAnswer(disableMessage.getMessageId());
        // OPTIONAL: show a dialog indicating the message that was received
        mainActivity.notifyApp(disableMessage);
    }
    public static enum DisableMessage {
        BLOCK(0), NOTIFY(1);
        private int type;
        private DisableMessage(int i) {
            type = i;
        }
        protected static DisableMessage fromJSON(JSONObject object) {
            String messageType = object.optString("messageType", null);
            DisableMessage disableMessage = null;
            for (DisableMessage n : values()) {
                if (messageType.equalsIgnoreCase(n.name())) {
                    disableMessage = n;
                }
            }
            if (disableMessage == null) {
                disableMessage = DisableMessage.BLOCK;
            }
            disableMessage.setMessage(object.optString("message", null));
            disableMessage.setDownloadLink(object.optString("downloadLink", null));
            disableMessage.setMessageId(object.optString("messageId", null));
            return disableMessage;
        }
        private String message;
        private String downloadLink;
        private String messageId;
        public String getMessage() {
            return message;
        }
        public void setMessage(String message) {
            this.message = message;
        }
        public String getDownloadLink() {
            return downloadLink;
        }
        public void setDownloadLink(String downloadLink) {
            this.downloadLink = downloadLink;
        }
        public String getMessageId() {
            return messageId;
        }
        public void setMessageId(String messageId) {
            this.messageId = messageId;
        }
    }
}
MyResponseListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.ibm.mobilefirst.android.listener;
import com.ibm.mobilefirst.android.MainActivity;
import com.worklight.wlclient.api.WLFailResponse;
import com.worklight.wlclient.api.WLResponse;
import com.worklight.wlclient.api.WLResponseListener;
public class MyResponseListener implements WLResponseListener {
    private MainActivity mainActivity;
    public MyResponseListener(MainActivity activity) {
        mainActivity = activity;
    }
    @Override
    public void onSuccess(WLResponse response) {
        // update the text field inside MainActivity with the response from the MobileFirst server
        mainActivity.updateConsole(response.getResponseText());
    }
    @Override
    public void onFailure(WLFailResponse wlFailResponse) {
        // update the text field inside MainActivity with the response from the MobileFirst server
        mainActivity.updateConsole(wlFailResponse.getResponseText());
    }
}
Now, let's modify our main activity. We will create an instance of WLClient, register our MyRemoteDisableChallengeHandler, and follow with a connection to the MobileFirst server.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package com.ibm.mobilefirst.android;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.ibm.mobilefirst.andorid.challengehandler.MyRemoteDisableChallengeHandler;
import com.ibm.mobilefirst.android.listener.MyResponseListener;
import com.worklight.wlclient.api.WLClient;
import com.worklight.wlclient.api.WLResponseListener;
public class MainActivity extends Activity {
    private WLResponseListener responseListener;
    private Button connectButton;
    private EditText textConsole;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // create the instance of WLClient
        final WLClient client = WLClient.createInstance(this);
        // register the customer remote disable challenge handler
        client.registerChallengeHandler(new MyRemoteDisableChallengeHandler(this));
        // connect to the server
        client.connect(responseListener());
        connectButton = (Button) findViewById(R.id.connectButton);
        textConsole = (EditText) findViewById(R.id.textConsole);
        // disable editing in the EditText field, this will be used for server response output
        textConsole.setKeyListener(null);
        // every time "Connect" is pressed, you will connect to MobileFirst Server
        connectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                client.connect(responseListener());
            }
        });
    }
    private WLResponseListener responseListener() {
        if (responseListener == null) {
            responseListener = new MyResponseListener(this);
        }
        return responseListener;
    }
    /*
     * disable app handler, this will show a dialog with an "Ok" button and a "Download" button that
     * will allow the user to download a new app from the Play store, App Center, or the internet
     */
    public void disableApp(final MyRemoteDisableChallengeHandler.DisableMessage disableMessage) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                connectButton.setEnabled(false);
                AlertDialog.Builder builder = notificationDialog(disableMessage);
                builder.setNeutralButton("Download New Version", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // download the new .apk (Play Store, App Center, etc...)
                    }
                });
                builder.create().show();
            }
        };
        runOnUiThread(runnable);
    }
    /*
     * app notify handler, this will show a dialog when the app is marked as "Active, Notifying"
     * in the MobileFirst Operations Console
     */
    public void notifyApp(final MyRemoteDisableChallengeHandler.DisableMessage message) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                notificationDialog(message).create().show();
            }
        };
        runOnUiThread(runnable);
    }
    /*
     * notification helper, this will set the title of the dialog and add a default "OK" button
     * to dismiss it
     */
    private AlertDialog.Builder notificationDialog(MyRemoteDisableChallengeHandler.DisableMessage message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
            }
        });
        String title;
        switch (message) {
            case NOTIFY:
                title = getString("Notification");
                break;
            default:
                title = getString("DISABLED App");
        }
        builder.setTitle(title);
        builder.setMessage(message.getMessage());
        return builder;
    }
    /**
     *
     * this will be called by the connection listener to show the response text of each connection
     * to the MobileFirst server
     */
    public void updateConsole(final String content) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                textConsole.setText(content);
            }
        };
        runOnUiThread(runnable);
    }
}

iOS

First, you will need a native iOS application with the IBM MobileFirst SDK included in it; for more information on how to do this visit the Getting Started section for iOS. Next, let's create the challenge handler and response delegate, MyRemoteDisableChallengeHandler and MyResponseDelegate respectively. MyRemoteDisableChallengeHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#import <Foundation/Foundation.h>
#import "WLChallengeHandler.h"
#import "ViewController.h"
@interface MyRemoteDisableChallengeHandler : WLChallengeHandler
@property (nonatomic) ViewController *viewController;
-(id)initWithController:(ViewController *)viewController;
@end
[/code]
[code lang="objc" title="MyRemoteDisableChallengeHandler.m"]
#import "MyRemoteDisableChallengeHandler.h"
#import "ViewController.h"
@implementation MyRemoteDisableChallengeHandler
-(id)initWithController:(ViewController *)viewController {
    self = [super initWithRealm:@"wl_remoteDisableRealm"];
    if (self) {
        self.viewController = viewController;
    }
    return self;
}
-(void)handleSuccess:(NSDictionary *)success {
    // the app is not notifying nor disabled. normal process can follow
}
-(void)handleChallenge:(NSDictionary *)challenge {
    /*
     * the app is not disabled but it is marked as "Active, Notifying"
     * submit challenge answer, so that the normal flow of the app continues.
     *
     * NOTE: if you also want to disable the app when it's notifying then don't call
     * answerChallenge and follow a similar approach to the handleFailure method
     */
    NSString *messageId = [challenge valueForKey:@"messageId"];
    [self answerChallenge:messageId];
    // OPTIONAL: show an alert indicating the message that was received
    [self.viewController notifyApp:[challenge valueForKey:@"message"]];
}
-(void)handleFailure:(NSDictionary *)failure {
    /*
     * the app is disabled. disabled the connect button in the view controller
     * you could do other things like changing the colors and show a different view
     */
    [self.viewController disableApp:[failure valueForKey:@"message"]];
}
-(void)answerChallenge:(NSString*) messageId {
    // submit answer so that challenge is satisfied
    NSMutableDictionary *remoteDisableDict = [[NSMutableDictionary alloc]init];
    [remoteDisableDict setValue:messageId forKey:@"string"];
    [self submitChallengeAnswer:remoteDisableDict];
}
@end
MyResponseDelegate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#import <Foundation/Foundation.h>
#import "WLDelegate.h"
#import "ViewController.h"
@interface MyResponseDelegate : NSObject  <WLDelegate>
@property (nonatomic) ViewController *viewController;
-(id)initWithController:(ViewController *)viewController;
@end
[/code]
[code lang="objc" title="MyResponseDelegate.m"]
#import "MyResponseDelegate.h"
@implementation MyResponseDelegate
-(id)initWithController:(ViewController *)viewController {
    self = [super init];
    if(self) {
        self.viewController = viewController;
    }
    return self;
}
-(void)onSuccess:(WLResponse *)response {
    [self.viewController updateConsole:[response responseText]];
}
-(void)onFailure:(WLFailResponse *)response {
    [self.viewController updateConsole:[response responseText]];
}
@end
Now, let’s modify the main view controller. We will register our MyRemoteDisableChallengeHandler and follow with a connection to the MobileFirst Server.

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextView *consoleTextView;
-(void)updateConsole:(NSString*)content;
-(void)notifyApp:(NSString*)message;
-(void)disableApp:(NSString*)message;
@end
ViewController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#import "ViewController.h"
#import "WLClient.h"
#import "MyRemoteDisableChallengeHandler.h"
#import "MyResponseDelegate.h"
@interface ViewController () {
    MyResponseDelegate *responseDelegate;
}
@property (weak, nonatomic) IBOutlet UIButton *connectButton;
- (IBAction)connectButtonClick:(id)sender;
@end
@implementation ViewController
@synthesize connectButton;
- (void)viewDidLoad {
    [super viewDidLoad];
    responseDelegate = [[MyResponseDelegate alloc] initWithController:self];
    // register the customer remote disable challenge handler
    [[WLClient sharedInstance] registerChallengeHandler:[[MyRemoteDisableChallengeHandler alloc] initWithController:self]];
    // connect to the server
    [[WLClient sharedInstance] wlConnectWithDelegate:responseDelegate];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
- (IBAction)connectButtonClick:(id)sender {
    [[WLClient sharedInstance] wlConnectWithDelegate:responseDelegate];
}
-(void)updateConsole:(NSString*)content {
    NSLog(@"ynunez content %@", content);
    [self.consoleTextView setText:content];
}
/*
 * app notify handler, this will show an alert when the app is marked as "Active, Notifying"
 * in the MobileFirst Operations Console
 */
-(void)notifyApp:(NSString*)message {
    UIAlertView *alert = [self alertBuilder:@"App Notification"];
    [alert setMessage:message];
    [alert show];
}
/*
 * disable app handler, this will show an alert with an "Ok" button and a "Download" button that
 * will allow the user to download a new app from the App Store, App Center, or the internet
 */
-(void)disableApp:(NSString*)message {
    [connectButton setEnabled:NO];
    [connectButton setBackgroundColor:[UIColor grayColor]];
    [connectButton setTitleColor:[UIColor blackColor] forState:UIControlStateDisabled];
    UIAlertView *alert = [self alertBuilder:@"App Disabled"];
    [alert setMessage:message];
    [alert addButtonWithTitle:@"Download Update"];
    [alert show];
}
/*
 * alert helper, this will set the title of the alert and add a default "OK" button
 * to dismiss it
 */
-(UIAlertView*)alertBuilder:(NSString*) title {
    UIAlertView *alert = [[UIAlertView alloc] init];
    [alert setTitle:title];
    [alert setCancelButtonIndex:[alert addButtonWithTitle:@"OK"]];
    return alert;
}
@end

Final Thoughts

In this blog post we have learned how to implement our own "Remote Disable" behavior for native Android and iOS apps. Our application shows the response from the MobileFirst server when the app is marked "Active", shows popup with a message when the app is marked "Active, Notifying", and disables the "Connect" button and shows popup with a download button when the app is marked "Access Disabled".
Last modified on May 01, 2016
Share this post: