|
Historify
Event aggregator application that supports openness.
Introduction
The key element of Historify is to support openness: with the help of the Historify BridgeLibrary, developers could easily connect their application with Historify's aggregation engine: any application could generate events and provide options for interaction. The aim of wiki page is to give an introduction for Android developers on how to make use of Historify's services in their works. Basic conceptsEvent aggregationBy event, we means something that occurred in the past between the owner of the device and one of her/his contacts and could be associated with an application that is currently installed on the device. Several examples are: a phone call in the past, an SMS sent by the contact, a message in the Facebook application. During event aggregation, Historify collects these event's data stored in different ContentProviders, filters for a particular contact and displays them on a time-ordered list (timeline). By event provider, we mean a single source of events. Basically, there are two kinds of event providers:
Internal sources are realized by Historify itself and handle system provided events (Telephony and Messaging). They are added to the list of sources by default. External sources are realized by 3rd party applications (a.k.a. clients) and added to the list of sources on installing the client application's package. After registering an event source, external applications are able to share their internal events with Historify (this type of source is called a SharedSource). There is also a special external source called QuickPost (realized by Historify) which is intended to simplify event sharing for client applications: With the help of this provider, the client could simply add an event to the timeline by a single Intent call instead of registering a provider and storing events internally.
The conceptual differences between the two ways of adding data (SharedSource and QuickPost) are described in the next section. Ways of adding dataHistorify provides a Service-based Intent interface (the Bridge) for client applications to add their event data to the aggregated event set. To facilitate dealing with the Intent calls, the Historify BridgeLibrary have been introduced. The library offers two ways of adding event data to the timeline:
QuickPost is a simple way of adding an event to the timeline by a single Intent call. It is intended to be used by applications that generate occasional, user-driven events. Good example could be a video rental service which lets the user set whom the rented movie she watched with. The app could ask the user if she wanted to post the event ("we watched the movie X") to Historify, and if the users allows it, use the QuickPost interface to add the event to the timeline. Client application could also get connected to Historify by registering a SharedSource, which case all the necessary components (BroadcastReceiver, ContentProvider) are realized by the client application. Since the events are stored and managed by the client itself, Historify only uses the URI-based ContentResolver interface to query the events of a contact when aggregating event data. This method is intended to be used by applications with a background service that synchronizes large set of event data. Good example could be a Facebook client application. Both QuickPost and SharedSource let the client application describe the event source with the same data fields including the name of the source, brief description, and an URI of an icon resource which is used when displaying events of the client. Several optional fields are only supported by the SharedSource interface, e.g. by defining a custom icon loading strategy for a SharedSource, a client application could provide customized icons for each event instead of using the source's default icon on every event. Both QuickPost and SharedSource let the client application describe the shared event with the same data fields including time of the event, the contact its associated with, the originator of the event (user, contact, both) and brief description. Several optional fields are only supported by the SharedSource interface. Detailed description of the supported fields is available at Historify#Dealing_with_QuickPost and Historify#Dealing_with_a_SharedSource Note that in case of QuickPosting, both source metadata and event data is passed via the Intent, while a SharedSource's metadata is passed to Historify when registering the source, and only event data is passed on event aggregation. The following table compares the two described methods by summarizing the differences listed above.
Source configurationSource configuration is available for client applications using the SharedSource method. When registering a SharedSource, a client could provide an action of an Intent fired by Historify when the user selects the 'More' button of a the registered source on the 'my sources' Activity. Based on this field, the client application could define an Activity to let the user customize the behavior of the client (e.g. which type of data is shared) and integrate it seamlessly with the source settings of Historify.
Note that QuickPost does not support source configuration. See Historify#Dealing_with_a_SharedSource for details. InteractionHistorify offers a set of options for the user to create new events (e.g. initiate phone calls, compose a new message). By providing an optional field supported by both QuickPost and SharedSource, client applications could register their own shortcuts with an Intent for interaction appropriate for the client. For example, a movie rental app could launch an Activity to rent a movie recommended for the contact based on rental history and also may send a message to the contact to invite her/him to watch the selected movie. A Facebook client could launch the message composer. The shortcut is described with an action label and the Intent to be fired when the interact action gets selected. The default icon of your event source will be used in the menu when displaying your shortcut.
See Historify#Dealing_with_QuickPost and Historify#Dealing_with_a_SharedSource for details. BridgeLibrary API documentationThe Historify BridgeLibrary is a Java library allowing Android applications to access Historify's Intent-based interface. With the help of this package, Android developers could connect their applications with the event aggragetion engine by registering their own event sources, publishing events and adding custom Intents to integrate their applications' functionality into the UI of Historify. The latest version of the library, javadoc, and sample applications that uses the library's functions are available for download. See Historify#Further_materials. UsageTo start working with the library, you will need to download the latest jar build available at the Download section. See Historify#Further_materials for list of related downloads. To set up the library in the Eclipse IDE environment, the following steps are required:
To see your registered source, generated events, etc. in the event aggregation environment, it is recommended to install the latest version of the Historify application on your testing device / emulator. The .apk is available for download. See Historify#Further_materials. It is also recommended to add the following permission to your AndroidManifest.xml file: <uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" /> This step is optional, however, it is intended to inform the user that your application is able to interact with the services of Historify. After setting up a project, you could use the services of the Bridge by instantiating a HistorifyBridge class, which is a component responsible for communicating with Historify's Bridge Service. HistorifyBridge bridge = new HistorifyBridge(R.drawable.icon); The functions of the Bridge class are described in the forthcoming sections. Dealing with QuickPostPurposeQuickPost is a simple way of adding an event of an external application to the timeline by a single Intent call. It is intended to be used by applications that generate occasional, user-driven events. For a comparison to other ways of creating events, please check out the Historify#Ways_of_adding_data section. Can I QuickPost?Writing an application that is intended to use the QuickPost interface, it is elementary to determine that Historify is installed and the Bridge is enabled on the user’s device. If Historify is not present in the current environment, it is unnecessary to set up a QuickPostContext, post events, or even display the UI elements used for QuickPosting. To determine that there is a Service able to handle your QuickPosts, you may use the Bridge's canQuickPost() method: boolean enableQuickPosting = bridge.canQuickPost(context);
if(enableQuickPosting) {
//setting up a QuickPostContext
} else {
//disable functions, hide UI elements, etc.
}Setting up a QuickPostContextBefore your application could produce QuickPosts, it is required to set up a proper QuickPostContext. The context stores all the information about your application necessary for displaying content in Historify. The following parameters are supported:
The following code snippet sets up a context with some basic parameters: QuickPostContext quickPostContext = new QuickPostContext(
"My App",
"This is my app with some cool events.",
null, //icon URI is null, default app icon will be used
SOURCE_VERSION);
quickPostContext.setEventIntent("com.example.myapp.ACTION_SHOW_EVENT");
quickPostContext.setInteractIntent("com.example.myapp.ACTION_INTERACT", "Open My App");
bridge.setQuickPostContext(quickPostContext);Note that you have to set up a context only once (e.g. after instantiating the HistorifyBridge class. All events posted after setting up the context will use the information you provided in setQuickPostContext(). Adding QuickPost eventsAfter successfully setting up a QuickPostContext, your event(s) could be added to the timeline by simply calling HistorifyBridge.quickPost(Context context, EventData eventData). The eventData parameter used to describe the event that has to be added. If the parameter is valid, the BridgeLibrary passes it to Historify supplemented by the metadata of your source you provided earlier. The data will be saved in Historify considering the following rules:
The following table summarizes which parameters are supported by the EventData class hence could be used when posting events.
A code snippet that illustrates usage: String contactLookupKey = getSelectedContact(); //provides a contact key EventData eventData = new EventData( "event_1", contactLookupKey, System.currentTimeMillis(), "This is an incoming event.", Originator.contact); bridge.quickPost(context, eventData); PersistenceYour QuickPosts, including the metadata of your source, will be stored persistently by Historify. Note that you are not able to modify previously added events, however, you are able to modify previously added source metadata by incrementing the version code. Your events and metadata will be removed if your application's package gets removed from the system. Dealing with a SharedSourcePurposeClient application could get connected to Historify by registering a SharedSource. This method of creating events is intended to be used by applications with a background service that synchronizes large set of events. The ContentProvider that stores the data is realized by the application itself, thus it is able to control the full life-cycle of the events. Historify only uses the URI-based ContentResolver interface to query the events of a contact when aggregating event data. For a comparison to other ways of creating events, please check out the Historify#Ways_of_adding_data section. Registering a SharedSourceTo add an external event provider to Historify, client application have to register the event source via the Historify Bridge Service. The process of the source registration is the following:
Note that the Bridge also supports the registration of SharedSources that have been installed before Historify. To detect these applications, Historify also sends the BROADCAST_REQUEST_REGISTER_SOURCE on its first startup, but without adding the package name Intent extra. In this case, all clients will respond to the Intent, and they all will be calling the BridgeService to register themselves. As a conclusion, the client application should declare a Broadcast Receiver in its manifest to detect the BROADCAST_REQUEST_REGISTER_SOURCE Intent, and make a call to the Bridge to register its SharedSource. To facilitate dealing with this process, a special BroadcastReceiver ( HistorifyBridge.RequestReceiver) is included in the library. The following steps guide you through on the process of adding the Receiver to your project and invoking the method for source registration.
public class HelloReceiver extends HistorifyBridge.RequestReceiver {
@Override
protected void onRequestRegister(Context context) {
//this method gets called when BROADCAST_REQUEST_REGISTER_SOURCE
// is addressed to your application
}
}<receiver android:name=".HelloReceiver">
<intent-filter>
<action
android:name="org.openintents.historify.REQUEST_REGISTER_SOURCE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
The following code snippet registers a SharedSource with some basic parameters: public class HelloReceiver extends HistorifyBridge.RequestReceiver {
@Override
protected void onRequestRegister(Context context) {
SourceData sourceData = new SourceData(
"My Source",
YOUR_SHARED_SOURCE_AUTHORITY,
"My SharedSource with some cool events.",
null, //icon URI is null
SOURCE_VERSION);
sourceData.setConfigIntent("com.example.myapp.ACTION_CONFIG");
sourceData.setEventIntent("com.example.myapp.ACTION_SHOW_EVENT");
sourceData.setInteractIntent(
"com.example.myapp.ACTION_INTERACT",
"Open My App");
sourceData.setIconLoadingStrategy(
IconLoadingStrategy.useSourceIcon);
bridge.registerSource(context,sourceData);
}
}After your source got registered, Historify's event aggregation engine queries the ContentProvider of which authority have been passed to the register method. See next section for details. Providing eventsAfter your source got registered, Historify's event aggregation engine queries the ContentProvider of which authority have been passed to the register method. This must be a ContentProvider that contributes the events of the contacts in the format understood by Historify. The following table summarizes the URIs that a SharedSource's ContentProvider may support by default. Note that only a subset of the URIs are used currently by Historify, the others are added for the client's benefit and for future development.
The result of a query of the URIs listed above should be a data set containing 0 or more rows of event data arranged in a format defined by the Events class. The supported fields are listed in the following table. The rows are always have to be ordered by the PUBLISHED_TIME field in a descending order, to support fast event aggregation.
To help dealing with the URIs and event fields, the EventsProvider and the Events helper class are included in the BridgeLibrary. EventsProvider is ContentProvider which has some predefined URI matching functionality and protected methods for querying the different set of events. Feel free to override these methods to make them suit your needs. Following the steps listed above, you might be able to use the EventProvider class in your application.
public class HelloProvider extends EventsProvider {
public static final String AUTHORITY =
"com.example.myapp.provider";
@Override
protected String getAuthority() {
return AUTHORITY;
}
@Override
protected Cursor queryEventsForContact(String lookupKey) {
//this gets called when the event aggregator queries
//your provider for the events associated with a particular
//contact.
}
}
<provider android:name=".HelloProvider" android:authorities="com.example.myapp.provider" android:exported="true"> </provider> @Override
protected Cursor queryEventsForContact(String lookupKey) {
MatrixCursor mc = new MatrixCursor(new String[] {
Events._ID,
Events.CONTACT_KEY,
Events.EVENT_KEY,
Events.MESSAGE,
Events.ORIGINATOR,
Events.PUBLISHED_TIME
});
mc.addRow(new Object[] {
1l,
lookupKey,
null,
"Hello, SharedSource!",
Events.Originator.contact,
System.currentTimeMillis()
});
return mc;
}
Based on the snippets of this and the previous section, you should be able to register a SharedSource and share events with Historify. Check out the Hello, SharedSource! tutorial section for step-by-step instructions. Notify on changeHistorify uses Android's ContentObserver mechanism to refresh aggregated data if the content of an event provider changes. If a cursor is opened via an EventsProvider, its notification URI is set to the URI of the provider's events path by default. When a component posts a change notification by calling ContentResolver.notifyChange(Uri uri, ContentObserver observer), where the uri is set to the URI of an aggregated EventsProvider, Historify gets notified and the displayed data could be refreshed. Working with your own SharedSource, you are able to send notification by calling EventsProvider.onEventsChanged() right after updating your EventsProvider. If the data you provide is not stored in the EventProvider itself, but in an other provider or custom data source, you should call ContentResolver.notifyChange(Uri uri, ContentObserver observer) right after updating the data source, where the uri is set to content://YOUR_SHARED_SOURCE_AUTHORITY/events to get the events of your SharedSource re-queried. When inserting a recent event for a contact, consider calling ContactsContract.Contact.marksAContacted(ContentResolver resolver, long contactId) to notify the system about the recent interaction. The contact associated with the contactId you provide will be shown at the very beginning of the 'recently contacted' list on Historify's welcome screen. Regarding the method, see Android SDK reference. Note that in case of QuickPosting, markAsContacted() is called by Historify automatically. PersistenceYour SharedSource's metadata will be stored persistently by Historify. Note that since a SharedSource's events are managed by the client application itself, you are able to update / delete previously added events and add new events to satisfy on your application's needs. You are also able to modify previously added source metadata by incrementing the source's version code when releasing a new version of your application. Note that your SharedSource will be unregistered if your application's package gets removed from the system (not considering re-installs). TutorialsHello, QuickPostThis Hello, QuickPost! tutorial is designed to get you started quickly with Historify's QuickPosting interface. It is assumed that:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.quickpost"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
...
</application>
</manifest>//main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello, QuickPost!"
/>
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Post"
/>
</LinearLayout>
//MainActivity.java:
public class MainActivity extends Activity {
private TextView text;
private Button button;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
text = (TextView)findViewById(R.id.text);
button = (Button)findViewById(R.id.button);
}
}
HistorifyBridge bridge;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
...
bridge = new HistorifyBridge(R.drawable.icon);
}
private static final int QP_SOURCE_VERSION = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...
bridge = new HistorifyBridge(R.drawable.icon);
boolean enabledQuickPost = bridge.canQuickPost(this);
if(enabledQuickPost) {
QuickPostContext quickPostContext = new QuickPostContext(
"Hello, QuickPost!",
"This is my QuickPost test app.",
null, //icon URI is null, default app icon will be used
QP_SOURCE_VERSION);
bridge.setQuickPostContext(quickPostContext);
button.setOnClickListener(new OnPostListener());
} else {
button.setEnabled(false);
}
}
private class OnPostListener implements View.OnClickListener {
public void onClick(View view) {
String contactLookupKey =
getContactKey(); //provides a contact key
EventData eventData = new EventData(
"event_1",
contactLookupKey,
System.currentTimeMillis(),
text.getText().toString(),
Originator.contact);
bridge.quickPost(MainActivity.this, eventData);
}
}
public String getContactKey() {
Cursor c = null;
String retval = null;
try {
c = getContentResolver().query(
Contacts.CONTENT_URI,
new String[] { Contacts.LOOKUP_KEY },
Contacts.IN_VISIBLE_GROUP + " = 1",
null,
Contacts.DISPLAY_NAME);
if (c.moveToFirst()) {
retval = c.getString(0);
} else {
throw new IllegalStateException("No contacts.");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null)
c.close();
}
return retval;
}<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.quickpost"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
...
</manifest>
Hello, SharedSourceThis Hello, SharedSource! tutorial is designed to get you started quickly with Historify's SharedSource registration and event sharing mechanism. It is assumed that:
contains simple SharedSource that provides a mock event for every contact in the user's Contacts application and shares it with Historify. To get started, create a new Android project. The required minimum API level is 5.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sharedsource"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
...
</application>
</manifest>package com.example.sharedsource;
import org.openintents.historify.services.bridge.HistorifyBridge.RequestReceiver;
import android.content.Context;
public class HelloReceiver extends RequestReceiver {
@Override
protected void onRequestRegister(Context context) {
// TODO Auto-generated method stub
}
}<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sharedsource"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
...
<receiver android:name=".HelloReceiver">
<intent-filter>
<action
android:name="org.openintents.historify.REQUEST_REGISTER_SOURCE"
/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
</application>
</manifest>private static final int SOURCE_VERSION = 1;
@Override
protected void onRequestRegister(Context context) {
SourceData sourceData = new SourceData(
"Hello, SharedSource!",
HelloProvider.AUTHORITY,
"This is my SharedSource test app.",
null, //icon URI is null, default app icon will be used
SOURCE_VERSION);
HistorifyBridge bridge = new HistorifyBridge(R.drawable.icon);
bridge.registerSource(context,sourceData);
}
package com.example.sharedsource;
import org.openintents.historify.data.providers.EventsProvider;
import android.database.Cursor;
public class HelloProvider extends EventsProvider {
public static final String AUTHORITY =
"com.example.sharedsource.provider";
@Override
protected String getAuthority() {
return AUTHORITY;
}
@Override
protected Cursor queryEventsForContact(String lookupKey) {
// TODO Auto-generated method stub
return null;
}
}<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sharedsource"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="org.openintents.historify.permission.USE_BRIDGE" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
...
<provider android:name=".HelloProvider"
android:authorities="com.example.sharedsource.provider"
android:exported="true">
</provider>
</application>
</manifest>@Override
protected Cursor queryEventsForContact(String lookupKey) {
MatrixCursor mc = new MatrixCursor(new String[] {
Events._ID,
Events.CONTACT_KEY,
Events.EVENT_KEY,
Events.MESSAGE,
Events.ORIGINATOR,
Events.PUBLISHED_TIME
});
mc.addRow(new Object[] {
1l,
lookupKey,
null,
"Hello, SharedSource!",
Events.Originator.contact,
System.currentTimeMillis()
});
return mc;
}
Further materialsHistorify 1.0-b1:
Historify BridgeLibrary:
LendMe: QuickPost showcase application:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Historify is an Android application for managing contact-related events gathered from various sources (call log, messaging app, 3rd party applications and extensions). The different events are aggregated seamlessly and displayed in an integrated view. The application also make interaction easier by offering options to create new events (e.g. initiate phone calls, compose a new message).




A project I've been working on a few months ago is quite similar to what you did (read, aggregating events from the phone). It's called JOPPFM (http://joppfm.tomtasche.at/).
Unfortunately the recent Gmail updates broke almost everything...