Android How to Documentation

This document aims at describing the main elements necessary for development of a new SmartCampus Android app and its integration with the existing apps and app container. Starting from the overall architecture, it explains the mechanisms for the authentication, interactions with remote services, as well as the support libraries for data storage/synchronization and for the common tasks.

Architecture

The SmartCampus Android applications are not totally independent. They rely upon presence of the “container” application that:

  • provides the dashboard, from which one can access the apps;
  • allows for managing the application updates;
  • provides the SmartCampus user authentication that is essential for accessing the SmartCampus platform and the Web services;

Note that in order to restrict the access only to the apps that make part of the SmartCampus apps ecosystem and to protect the sensitive resources (e.g., use credentials), the container poses some restrictions on the app declaration, deployment, and execution. Specifically, the applications should have the same sharedUserId and be signed with the same certificate. Furthermore, some of the exported elements (activities and services) are additionally protected with the corresponding “signature”-protected permissions.

Development note: in these settings the applications developed and tested locally may be in conflict with the “official” apps already installed. For this reason, the old apps should be uninstalled and installed from the development environment with the same debug certificate.

You can find here a document describing in detail the architecture of the SmartCampus platform.

Application Configuration

A SmartCampus application should adhere to the following configuration constraints and requirements:

  • define the following shared user id and the label in AndroidManifest.xml:
1
2
android:sharedUserId="eu.trentorise.smartcampus.shared"
android:sharedUserLabel="SmartCampus"

  • declare references to the corresponding libraries in the project configuration. For example, for Eclipse IDE the libraries are declared in Project Properties/Android section. The dependencies may include, in particular:

    • authentication support library (android.security.client)
    • storage support (android.storage)
    • common utility library (smartcampus.android.common)
    • communication protocol support library (protocol-carrier)

    screen

    Development note: in order to make the library available to the project it is necessary to add them to the Eclipse workspace as library projects. The library sources are available at https://github.com/smartcampuslab

  • Define the target Android SDK version constraints in AndroidManifest.xml. The minimal library should be:

1
<uses-sdk android:minSdkVersion="8" ... />

Authentication

The access to the remote SmartCampus services requires the requests to be accompanied with the user authentication token. The token is emitted, renewed, and invalidated via the Acess Control service. The actual authentication is delegated to the external identity managers so that the user credentials are not available to the apps. Currently, the authentication relies on the Shibboleth protocol.

On the client side, the authentication and token management is performed using the android.security.client library. The library is used to acquire, store, and retrieve the user access tokens. The library uses Android Account API, which natively handles creation and update of the accounts and access tokens.

To acquire the authentication token to use when calling remote service, the following steps are necessary:

  1. get the reference to the SCAccessProvider:
  2. 1
    
    SCAccessProvider accessProvider = new AMSCAccessProvider();
  3. Acquire the token using one of the methods exposed by the access provider:
    • getAuthToken(Activity activity, String authority): retrieve access token from the running activity. If the token is stored locally, it is returned, otherwise a dedicated Authentication activity starts for result. The calling activity should implement the Activity.onActivityResult method to process the obtained token. The authority attributes defines the required authentication authority. If not specified, the user can login with any authority.
    • getAuthToken(Context ctx, String authority, IntentSender intentSender): Retrieve the authentication token from the an arbitrary context. If the token is stored locally, it is returned, otherwise a notification is added to the notification bar. If the user accesses the notification, the Authentication Activity starts and publishes the corresponding authentication result broadcast if successful. In case of implementation based on AccountManager infrastructure (eu.trentorise.smartcampus.ac.authenticator.AMSCAccessProvider), the broadcast with action AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION is propagated. In case of implementation based on local storage(eu.trentorise.smartcampus.ac.embedded. EmbeddedSCAccessProvider), a broadcast with action eu.trentorise.smartcampus.account.AUTHTOKEN_CHANGED is sent.
    • getAuthToken(Context ctx, String authority): Retrieve the authentication token from the an arbitrary context. If the token is stored locally, it is returned, otherwise the Authentication Activity starts, which, upon successful completion sends the specified intent.
    • readToken(Context ctx, String authority): Read token from the local cache without additional requests if the token is missing.
  4. If the token is requested from an activity, implement the Activity.onActivityResult method to process the obtained token.
  5. Declare the necessary permissions in AndroidManifest.xml:
  6. 1
    
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>

Data Storage

The android.storage library provides an abstraction layer for Android to perform principal data management operations given the Java Beans object model. The main case studies covered by the library refer to

  • store the application data objects locally in database;
  • store the application data remotely on the server;
  • support continuous incremental data synchronization with remote services.

The abstraction relies upon CRUD (create/read/update/delete) operations, providing the necessary extensions for each of the case studies. In this way, the switch from, e.g., remote storage to the local one is simplified as the object model and the core interfaces are equal.

Remote Storage

To use the library for the remote storage, the following steps are necessary:

  1. Define the data model of the objects. The required data model has to be defined as Java Beans that inherit from eu.trentorise.smartcampus.storage.BasicObject. Note that the the client-side java classes should have the same signature as their remote counterparts used to populate the REST/JSON objects. This is required for the correct data transformation on the client side.

  2. Instantiate the eu.trentorise.smartcampus.storage.remote.RemoteStorage class passing the Context, appToken, authToken, host (remote host and port), and service (name of the remote API application prefix).

  3. Call the corresponding method of the storage class. See the javadoc for the eu.trentorise.smartcampus.storage.remote.IRemoteStorage interface for details.

The remote counterpart is expected to expose the REST interfaces with the following signature conventions:

  • for object creation: POST <host>/<service>/<canonical-name-of-object-java-class>. The passed JSON represents the instance of the corresponding Java bean class.

  • for object update: PUT <host>/<service>/<canonical-name-of-object-java-class>/<id>. The passed JSON represents the instance of the corresponding Java bean class to be updated. The id is the value of the id field of the updated object.

  • for object delete: DELETE <host>/<service>/<canonical-name-of-object-java-class>/<id>. No request body is passed. The id is the value of the id field of the deleted object.

  • for individual object access by ID: GET <host>/<service>/<canonical-name-of-object-java-class>/<id>. No request body is passed. The id is the value of the id field of the deleted object. Should return the JSON representation of the object of the specified class and with the specified id.

  • for object access by type: GET <host>/<service>/<canonical-name-of-object-java-class>. No request body is passed. Should return the JSON array representation of the objects of the specified class.

  • for object search: GET <host>/<service>/objects. Query object is passed as “filter” query parameters in JSON representation. Should return the JSON map with full java class names as the keys and the corresponding array of the objects of those classes matching the criteria.

  • for batch update: POST <host>/<service>/objects. Passes the JSON representation of the eu.trentorise.smartcampus.storage.remote.BatchData class describing the update. The class contains “created” map of the objects to be created (full java class name as a key and list of objects as value), “updated” map of the objects to be updated (full java class name as a key and list of objects as value), and “deleted” map of the objects to be deleted (full java class name as a key and list of objectIds as value).

Local storage

The implementation of the local Android storage is based on the SQLite database. This requires the mapping between the app object model and the table model of the underlying DB. The following steps are required in order to realize the local storage:

  1. Define the data model to be stored, i.e., te objects to be saved. The required data model has to be defined as Java Beans that inherit from eu.trentorise.smartcampus.storage.BasicObject.

  2. Define the storage configuration. To do this it is necessary to:

    1. implement the eu.trentorise.smartcampus.storage.db.StorageConfiguration interface. Specifically, define which Java Bean classes are stored in sync storage (method getClasses), what the name of the DB table for each bean to use (method getTableName), and which storage helper to use for each of the bean types.

    2. For each of the stored types implement the eu.trentorise.smartcampus.storage.db.BeanStorageHelper interface. Specifically, define the conversion of the data cursor row to the bean (method toBean), conversion of the bean to DB content (method toContent) and describe the object-specific column format (i.e., type and metadata in the SQLite format, method getColumnDefinitions).

  3. Instantiate the eu.trentorise.smartcampus.storage.sync.SyncStorage class passing the Context, appToken, database name and version, and the storage configuration.

  4. Manipulate the data of the storage using the corresponding methods.

Data synchronization support

The storage library supports data synchronization with the remote services. Specifically, the any of the stored object is versioned and the current DB object version is exploited in order to implement the incremental data synchronization: only the changed/new/deleted data is being transferred to/from the services. To perform the synchronization the SyncStorage class provides the implementation of the synchronize method, given the user authentication token, remote host, and service data.

The possible implementations of the scheduled data synchronization is described in Data Synchronization section.

Communication with SmartCampus Services

The client-server protocol defines the communication instructions between the application client (e.g., Android or Web Browser client) and the exposed VAS services and APIs. The generic model of the protocol structured as in the following figure.

screen

Specifically, the interaction between the device and the server is performed on top of the Protocol Carrier that implements the low level transport functionalities. On top of this component, the protocol implements various Protocol Modules that implement different commonly used scenarios for the SmartCampus platform. The modules are realized through two submodules: the client-side part and the server-side part. The app in this regard defines which of the modules have to be used and how. Depending on the specific module, the VAS should provide the implementation of some interfaces required by the module (e.g., notification interfaces or storage interfaces) both on the client and on the server.

The protocol carrier library provides the necessary functionalities to perform immediate and deferred requests (see the specification for details). The relevant classes are:

  • eu.trentorise.smartcampus.protocolcarrier.ProtocolCarrier: exposes the invokeSync(MessageRequest, AppToken, AuthToken) method to perform a syncnronous immediate request.

  • eu.trentorise.smartcampus.protocolcarrier.AsyncProtocolCarrier: exposes the methods like invokeAsync(MessageRequest, AppToken, AuthToken, Callback, DeferredConf) to make asynchronous request, activate desactivate the app messages using start(AppToken)/stop(AppToken), retrieve the list of messages, clear the list, or removing an individual message from it.

  • eu.trentorise.smartcampus.protocolcarrier.sync.AsyncService: the background service that manages the delivery of the pending messages and their persistence.

Data Synchronization

To synchronize locally stored data with the remote services, two approaches can be applied. The first use Android synchronization API (recommended for most of the situatuions), while another uses the android.storage library components to manage and perform periodic updates. The first one is best suited when there are no strong constraints on update timeliness but the updates should be performed even if the application and its background services are not started. The second one gives more control on when and how the synchronization takes place, but requires the application being activated in order to start synchronization cycle.

When it comes to the synchronization of the data storage implemented as in section Data Storage, the remote service should adhere to the following constraints:

  • the client-side java classes should have the same signature as their remote counterparts used to populate the REST/JSON objects.

  • the remote REST service should expose POST http interface at / with the following properties

  • since parameter representing the last version when the synchronization has been performed.

  • the request body represents in JSON format the synchronization information including updated map of objects created/updated on the client (full java class name as the key and the list of objects as the value) and deleted map of objects deleted on the client (full java class name as the key and the list of object IDs as the value).

  • the response body that represents in JSON format analogous information, i.e., version defining the server version on the moment of synchronization, updated map of objects created/updated on the server (full java class name as the key and the list of objects as the value), deleted map of objects deleted on the server (full java class name as the key and the list of object IDs as the value).

Synchronization with Android Sync Adapter API

The following steps should be performed in order to perform data synchronization.

  1. Implement and declare Android Content provider (extend android.content.ContentProvider) in the AndroidManifest.xml:
  2. 1
    2
    3
    
    <provider
         android:name=".MyProvider" android:exported="false" android:authorities="my.authority"
      android:syncable="true" android:label="my label" />  
    Note that the provider may have empty implementation if the synchronized data storage is not exposed to the other apps.
  3. Implement Sync Adapter (extends android.content.AbstractThreadedSyncAdapter) that performs actual data synchronization (method onPerformSync). Specifically, this may amount to calling SyncStorage.synchronize() method.
  4. Implement and declare synchronization service that returns a reference to the !IBinder interface exposed by the SyncAdapter implementation class.
  5. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     <service
         android:name=".MySyncService" android:exported="false" >
         <intent-filter>
             <action android:name="android.content.SyncAdapter" />
         </intent-filter>
    
         <meta-data
             android:name="android.content.SyncAdapter"
             android:resource="@xml/syncadapter" />
     </service>
    Note that the service points to the file describing the Sync Adapter metadata.
  6. Define the Sync Adapter metadata as follows:
  7. 1
    2
    3
    
    <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
        android:contentAuthority="my.authority"
        android:accountType="eu.trentorise.smartcampus.account"/>

Note the reference to the SmartCampus account type. This is required for correct synchronization under SmartCampus account.

Synchronization using Sync Storage API

android.storage library provides additional classes to implement periodic synchronization of the corresponding data storage. These APIs provides more flexible and programmabl way to perform and schedule the data synchronization at cost of less control over the synchronization activation and termination.

To use this approach, it is necessary to accomplish the following steps:

  1. Declare the use of eu.trentorise.smartcampus.storage.SyncStorageService (or its subclass) in AndroidManifest.xml:
  2. 1
    
    <service android:name=".MySyncStorageService" android:exported="false" />
  3. Instantiate synchronization manager referring to this service:
  4. 1
    
    SyncManager mSyncManager = new SyncManager(mContext, MySyncStorageService.class);
  5. Instantiate the synchronization configuration with
  6. 1
    
    SyncStorageConfiguration config = new SyncStorageConfiguration(sc, serviceHost, serviceAddress, syncIntervalInMillis);
  7. Activate periodic synchronization cycle
  8. 1
    
    mSyncManager.start(authToken, appToken, config);
  9. Perform other synchronization operations using the manager, such as stopping the synchronization cycle, restarting, or forcing immediate asynchronous call.

Common Tasks

The core applications and library expose a set of functionalities that may be used across other apps.

Creating and following a topic

Using the Community Manager app and the it is possible to create a news topic starting from an existing object of certain type. To do this, one has to

  1. create an instance of eu.trentorise.smartcampus.android.common.follow.FollowEntityObject with semantic entity of the object (should not be null), object “name”, and its semantic type. One of the following types is currently supported: “location”, “event”, “portfolio”, “experience”, “journey”, “narrative” (story), “computer file” (for an arbitrary file, including media).

  2. Invoke eu.trentorise.smartcampus.android.common.follow.FollowHelper.follow passing the calling Android activity and the above object. This will start the “follow” activity and the corresponding topic form. Note that this requires the CommunityManager application to be installed.

Sharing an object

Using the Community Manager app and the it is possible to share an existing object. To do this, one has to

  1. create an instance of eu.trentorise.smartcampus.android.common.sharing.ShareEntityObject with semantic entity of the object (should not be null), object “name”, and its semantic type. One of the following types is currently supported: “location”, “event”, “portfolio”, “experience”, “journey”, “narrative” (story), “computer file” (for an arbitrary file, including media).

  2. Invoke eu.trentorise.smartcampus.android.common.sharing.SharingwHelper.share passing the calling Android activity and the above object. This will start the “share” activity and the corresponding form. Note that this requires the CommunityManager application to be installed.

Tagging Dialog

A frequent common task in the SmartCampus apps is to provide the user with the possibility to annotate the objects with semantic/textual tags. To start the tagging dialog it is necessary to instantiate eu.trentorise.smartcampus.android.common.tagging.TaggingDialog passing

  • an instance of OnTagsSelectedListener interface that should handle the result of the tag selection;

  • an instance of TagProvider interface that returns a list of tag candidates given the textual prefix;

  • optional array of SemanticSuggestion instances that represent the currently associated object tags.

The simplest way to implement the TagProvider interface is to exploit the eu.trentorise.smartcampus.android.common.tagging.SuggestionHelper.getSuggestions method that, given the prefix, current execution context, platform host, authentication token and app token, provides the list of matching suggestions calling the remote service exposed by Community Manager application.

Geocoding

smartcampus.android.commons library provide helper methods to perform geocoding and reverse geocoding operations. In particular, using eu.trentorise.smartcampus.android.common.SCGeocoder one can use

  • findAddressesAsync to retrieve asynchronously the list of matching addresses for the given coordinates;

  • getFromLocationNameSC to retrieve the list of addresses matching the arbitrary text, optionally restricting to the specific region, country, and administrative area data.

  • getFromLocationSC to retrieve synchronously the list of matching addresses for the given coordinates.

Navigation Directions

In order to open the Journey Planner interface to drive to a specific point from the current user location or from another point one can use the following utility method:

  • eu.trentorise.smartcampus.android.common.navigation.NavigationHelper.bringMeThere providing the start/end address objects.

Visualising Objects

The class eu.trentorise.smartcampus.android.common.view.ViewHelper provides an utility methods to visualize the specified SmartCampus object (such as an event, story or portfolio):

  • viewInApp(Activity ctx, String type, long entityId, Bundle parameters) that given the object type (as in sharing/following), the semantic entity ID and (optionally) specific parameters.

  • viewInApp(Activity ctx, String type, String objectId, Bundle parameters) that given the object type (as in sharing/following), the object ID and (optionally) specific parameters.