>  >  

Open Xerox Android Library

If you are writing an Android application that interacts with Open Xerox, the Open Xerox Android Library will prove useful.

This Java library provides helper functions for user authentication with OAuth2, as well as for calling Open Xerox REST services from an Android application.

Users can log thanks to their Open Xerox, S3, Google or Facebook account.

The APK library can be downloaded here. It contains code and resources. Moreover, the latest version and the source code are available on CodeX (Xerox users only), as well as the source code of a simple application that use the library. To use this lib without maven you need to include the commun Xerox Java library, available here.

You can also access the library Javadoc online.

Authentication

 Please look at the following code:

import com.openxerox.android.*;

public class MainActivity extends Activity {

  @Override
  public void onCreate(Bundle savedInstanceState) {

    // This service requires an Open Xerox login
    sessionMgr = SessionManager.getInstance();
    sessionMgr.init(getApplicationContext());
    final Session session = sessionMgr.loadSession();

    // check session
    if (session == null) {
      // Use Authentication Activity (dedicated login screen)
      Intent authIntent = new Intent(this, AuthenticationActivity.class);
      this.startActivity(authIntent);
    } else if (session.hasExpired()) {
      // calls session refresh
      session.refresh(new NewSessionListener() {
        // the listener can do actions when refresh succeds or fails

        @Override
        public void onRestResponse() {
          // response received : loads the received session
          session = sessionMgr.loadSession();
        }

        @Override
        public void onError(Exception arg0) {
          // resfesh failed : displays the authentication screen
          Intent authIntent = new Intent(activity, AuthenticationActivity.class);
          activity.startActivity(authIntent);
        }
      });
    }
  }
}

And here is what is happening:

  • First, we get an instance of the SessionManager, which is a singleton that manages all Open Xerox session aspects, and we initialize it with the Android context.
  • Next, we try to load the session from the application preferences. The library handles its own preference file where it stores the session information (and in particular the refresh token). Those preferences are updated each time a new session is created.
  • Then, we test whether the session was actually initialized. This will be false in the following cases:
    • if this is the first time the application is run on the device
    • If the user explicitly logged out the last time the application was used
    • If the application data were reset (through the Application settings menu).
  • If the session is not initialized, the Authentication activity is launched. This activity is a login screen asking for the user name and password on Open Xerox. A webview allows to enter Facebook/Google/S3 logins too. Once the form is submitted, the library handles the authentication process with the Open Xerox server through the OAuth2 protocol. The session token is then loaded, and the refresh token stored in the preferences file (only for Open Xerox account). If the webview contains a cookie with a valid token, the activity is finished. That's why a pseudo-automatic refresh is done with Google/Facebook/S3 when the token becomes invalid, but the ASP session permits to refresh it : the activity is terminated before displayed on screen.
  • If you do not want to use the Authentication activity provided with the library, you can use your own activity, and then use the SessionManager.login(username,password) method to run the authentication process.
  • Another possibility: the session was previously initialized, but the session has expired. In this case, you can simply run the refresh(...) method on the session to generate a new session token. The method has a NewSessionListener parameter, which permits to do something on failure or on refresh success. Here, when success, we load the new session, while on failure, we display the authentication screen.

We advise you to call the "check session" code when your application starts, and everywhere it displays a screen/menu which permits user to call a webservice. It will force the user to be logged to access those sections. You should then have code like that:

if (session == null) {
  Intent authIntent = new Intent(this, AuthenticationActivity.class);
  this.startActivity(authIntent);
} else if (session.hasExpired()) {
  session.refresh(...);
} else {
  // action to do if everything is ok
}

 

Listening Authentication

As you have read, you can use a NewSessionListener to catch success/failure of explicit authentication actions. But this listener doesn't permit you to catch background events, for example when an auto-refresh is done, or when a REST call without valid session forces the authentication screen to be displayed.

Then, you can add a global listener on authentication events, which will receive all events when authentication starts, ends, succeds (either a new session is created or the session is refreshed). This listener has to be set to the SessionManager singleton.

Please warn about listeners : if you decide to display something thanks to onObtainNewToken/onRefreshTokenSuccess, don't redo this in the onRestResponse of a NewSessionListener, or this object should be displayed twice!

An implementation of this interface should look like this:

SessionManager.setGlobalAuthenticationListener(new GlobalAuthenticationListener() {
			
  @Override
  public void onObtainNewToken(final String username, final int SessionType) {
    // Something to do when new token received
  }

  @Override
  public void onRefreshTokenSuccess(String username, int SessionType) {
    // Something to do when token refreshed
    // And reaffect the received session data
    session = sessionMgr.loadSession();
  }

  @Override
  public void startGettingToken() {
    // Something to do when refresh starts
    // example : show a wait pop-up
  }

  @Override
  public void endGettingToken() {
    // Something to do when refresh ends
    // example : hide the wait pop-up
    }
  });
}

 

The sessionType is an integer, but a String value can be used thanks to the Session.getTypeStringFor(int sessionType) function in order to display the session type. We recommand you to test permissions on obtaining new token in order to display a message if the login successed but the user can't access this service.

For more information about the RestCallListener, please read the "REST service call" section.

A full sample could look like this:

SessionManager.setGlobalAuthenticationListener(new GlobalAuthenticationListener() {
			
  @Override
  public void onObtainNewToken(final String username, final int SessionType) {
    // A new token is received.
    // We will try to ask for a first REST service in order to check if current account has permissions for this call
    RestTask task = new RestTask(context);
    task.setListener(new RestCallListener() {
					
      @Override
      public void onRestResponse(int arg0, Object arg1) {
        // The received session can access to the web service
        try {
          Toast.makeText(context, "You are now logged with "+Session.getTypeStringFor(SessionType)+" account",
            Toast.LENGTH_SHORT).show();
        } catch (Exception e) { }
      }
					
      @Override
      public void onError(int arg0, Exception arg1) {
        // The received session can't access to the web service
        try {
          Toast.makeText(context, "You are now logged with "+Session.getTypeStringFor(SessionType)+" account but are not able to use this service",
            Toast.LENGTH_LONG).show();
        } catch (Exception e) { }
      }
					
      @Override
      public void onByteTransfer(int arg0, long arg1, MultipartEntity arg2) {
        // nothing to do
      }
      });
      RestParams restParam = new RestParams(0, RestParams.RETURN_STRING, null, "https://services.open.xerox.com/RestOp/xips/mobile_clean");
      task.setFirstTryToRefresh(false);
      task.execute(restParam);
    }

    @Override
    public void onRefreshTokenSuccess(String username, int SessionType) {
      // just displays a toast with a refresh message
      try {
        Toast.makeText(context, "Your "+Session.getTypeStringFor(SessionType)+" session has been refreshed",
          Toast.LENGTH_SHORT).show();
      } catch (Exception e) { }
      session = sessionMgr.loadSession();
    }

    @Override
    public void startGettingToken() {
      // display a wait pop-up while contacting the server
      context.runOnUiThread(new Runnable() {
        public void run() {
          if (dialogue==null || !dialogue.isShowing())
            dialogue = ProgressDialog.show(context, "Loading", "Please wait...", true);
        }
      });
    }

    @Override
    public void endGettingToken() {
      // hides the wait pop-up when finished communication with server
      context.runOnUiThread(new Runnable() {
        public void run() {
          if (dialogue!=null)
            dialogue.dismiss();
        }
      });
    }
  });

 

In this example we suppose permission changes are never tested. But if needed, you can put the same permission test in onRefreshTokenSuccess and onObtainNewToken.

 

REST Service call

The library also provides classes to help developers call Open Xerox services through a REST API.

Let's use the example of the Language Identifier service, that takes a String parameter as input (named "document"), and that returns the guessed language (as a String).

Look at the following code:

 

import com.openxerox.android.*;
import com.openxerox.android.interfaces.RestCallListener;

public class MainActivity extends Activity implements RestCallListener {

  public static final int LANGID = 1;

(...)
  private void callLangID(String inputText) {
    // Create Rest Task object
    RestTask langIDTask = new RestTask(this.getBaseContext());
    // Rest parameter object. Constructor needs an ID, the return type, the result file name (if RestParams.RETURN_FILE, else null), and the URL of the operation
    RestParams params = new RestParams(LANGID,RestParams.RETURN_STRING,null,"https://services.open.xerox.com/RestOp/LanguageIdentifier/GetLanguageForString");
    // Then it is possible to add as many input parameters as needed, as long as they are Strings.
    params.addParam("document", inputText);
    // Register as listener to be called when processing is finished
    langIDTask.setListener(this);
    langIDTask.execute(params);
  }


  // called when the restTask completes
  public void onRestResponse(int restServiceID,Object response) {
    // the response contains:
    // - a String containing the file URI if RestParams.RETURN_FILE specified in RestTask constructor
    // - a String containing the response if RestParams.RETURN_STRING specified in RestTask constructor
    // - an InputStream containing the stream if RestParams.RETURN_STREAM specified in RestTask constructor
    switch (restServiceID) {
    case LANGID:
      TextView resultText = (TextView) findViewById(R.id.langIDresult);
      // Need to cast response to actual type.
      // Here, we know it is a String, so we use the toString() method
      // If the results are in JSON, you might need to parse the response.
      resultText.setText("Language: "+response.toString());
    }
  }

  // called when an error occurs
  public void onError(int restServiceID,Exception e) {
    // do something
  }

  // called when transfer progression changes
  public void onByteTransfer(int restServiceID, long byte_count, MultipartEntity entity) {
    // do something
  }
}

Here are the various steps:

  • First, we need to create a RestTask object
  • Then, we need a RestParams object, initialized with an integer ID (used to retrieve the result in the callback function), the wanted response type (RestParams.RETURN_FILE for a File, RestParams.RETURN_STRING for a String, or RestParams.RETURN_STREAM for an InputStream), the file name (if RestParams.RETURN_FILE), and the URL of the service REST operation.
  • You can then add parameters to the request. You add add them one by one with the addParam() method, or add a full Map with the addParamMap() method. If you need to send a whole files (e.g. images), you can use the addLocalFileParam(String fileParamName, String filePath)method. In this case, please note that the files must be stored on the device file system.
  • The last thing you need to do before executing the call is to register your object as a Listener of the task, or create a new one. That way, when the task is completed, it will run the onRestResponse() method of your object. In case of error, the onError() method will be called.
  • When you call execute method, a first verification step cheks if the current token is valid. If it isn't, it is automatically refreshed thanks to the refresh token, or displays the authentication screen. If the token is valid or automatically updated, the call will be done. If a manual authentication is required, the user will have to redo the action.
  • When one of the onRestResponse() or onError() methods is called, it has access to the call ID that you set earlier, the response object (if success) or an exception (if an error occured during the service execution). The response object can be either a String (the response body), a filename or a filestream.

 

OData call

 

Introduction

If you want to create an OData call, the call looks like a simple webservice call.
The two differences are the construction of the caller, and the response returned.

Instead of creating a RestTask, first create an ODataTask, and instead of creating a RestParam, create a ODataRequest.

The ODataTask is created thanks to the current context, which one has to be put into its constructor (as for the RestTask).

 

Building the ODataRequest

The ODataRequest differs a lot from the RestParam. The constructor is the same (a call ID, and the service URL), but methods building the request differs.

Parameters

Once your ODataRequest is built, you can access some methods permitting to build the request:
- setFilterParam(BooleanItem), which permits to receive only the objects corresponding to the given conditions
- setOrderParam(OrderByParam[]), to reorder the results
- setSelectParam(String[]), to receive for each item only the selected part of attributes
- setTopParam(int), to receive a maximum item count
- setSkipParam(int), to skip the first items into the result list

Ordering results

An OrderParam is an abstract class. The two implemetations are AscendantOrder and DescendantOrder. Those two classes have a simple constructor with the attribute name to sort. For example, if you want to sort a user list by ascendant first name, and descendant last loggin date, you can use this code:

params.setOrderParam(new OrderByParam[] { new AscendantOrder("Name"), new DescendantOrder("LogginDate") });

Filtering results

The filter parameter needs a final BooleanItem, corresponding to a condition to be checked. A boolean item can be obtained directly with a boolean item, or by comparing some items, like for each language. You can then start your filter expression with the FilterFactory which will simplify you the access of the different classes. You can then create a String, an Integer, a Date. Methods can create inner values, or get attributes values thanks to *Column methods. Example:

FilterFactory.createNumeric(12)

will correspond to the integer value : 12.

FilterFactory.getNumericColumn("Age")

will correspond to the numeric value of the "Age" attribute.

FilterFactory.createString("String")

will correspond to the text 'String' (with quotes into the request).

FilterFactory.getTextColumn("Comment")

will correspond to the text value of the "Comment" attribute. Into the request, the "Comment" name will not be quoted.

etc.

Once you have your first item is created, some functions permits you to contruct a full and complex filter.

String items can access to functions:
- StartsWith returning a booleanItem
- EndsWith returning a booleanItem
- Concat returning a String
- IndexOf returning a numericItem
- Lengthreturning a numericItem
- Replace returning a String
- SubString returning a String
- ToLower returning a String
- ToUpper returning a String
- Trim returning a String

Numeric items can access to the functions:
- Ceiling returning a numeric item
- Floor returning a numeric item
- Add returning a numeric item
- Sub returning a numeric item
- Mul returning a numeric item
- Div returning a numeric item
- Mod returning a numeric item
- Round returning a numeric item
- greaterOrEquals returning a booleanItem
- lowerOrEquals returning a booleanItem
- greaterThan returning a booleanItem
- lowerThan returning a booleanItem

Date items can access to the functions:
- Day/Days returning a numeric item
- Hour/Hours returning a numeric item
- Minutes/Minutes returning a numeric item
- Month/Months returning a numeric item
- Second/Seconds returning a numeric item
- Year/Years returning a numeric item

Boolean item can access to the functions:
- and returning a booleanItem
- or returning a booleanItem
- not returning a booleanItem

All Items can access to the functions:
- equals returning a booleanItem
- notEquals returning a booleanItem

 

Thanks to all those functions you can create a request with multiple conditions. For a call example:

ODataTask myODataTask = new ODataTask(this.getBaseContext());
ODataRequest params = new ODataRequest(MYWEBSERVICE,"https://mywebserviceurl/Accounts");
params.setFilterParam(FilterFactory.getTextColumn("Name").startsWithString("Xerox").or(FilterFactory.getTextColumn("Address").trim().length().gt(20)));
myOdataTask.execute(params);

With this filter, the request will call for all items with their "Name" attribute starting with "Xerox", or having an "Address" attribute with more than 20 characters (once trimmed). The answer will be returned thanks a listener affected to the ODataTask.

 

Define a listener

The listener looks like the RestCallListener, but doesn't have a method for transfer progression, because of the fact an oDataCall is an HTTP GET method.
Then, you juste have to implements those two methods:

myODataTask.setListener(new ODataListener() {

  // called when the ODataTask completes
  @Override
  public void onResponse(int restServiceID,JSONTokener response) {
    // the response contains a JSONArray or a JSONObject.
    switch (restServiceID) {
    case MYWEBSERVICEID:
      try {
        JSONArray array = (JSONArray) response.nextValue();
        // Please always use response.nextValue() and cast it to JSONArray or JSONObject.
        Log.w("JSON array : ", array.toString());
        doSomethingWith(array);
      } catch (JSONException e) {
        e.printStackTrace();
      }
    }
  }

  // called when an error occurs
  public void onError(int restServiceID,Exception e) {
    // do something
  }
});

 

Execute the call

Once you have put all needed parameters into your ODataRequest, you can call your

myODataTask.execute(odataRequest)

.
Please, ensure you have defined a listener for the myODataTask.

 

Maven Compilation

 

Please declare the apklib in your pom.xml (in the dependencies section) if you want to compile with maven.

<dependency>
  <groupId>OpenXerox</groupId>
  <artifactId>Android</artifactId>
  <version>0.0.1</version>
  <type>apklib</type>
</dependency>