diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..74897e1
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding//src/com/dattasmoon/pebble/plugin/NotificationService.java=UTF-8
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 44c6765..81b5e0a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2,18 +2,66 @@
+ android:versionCode="25"
+ android:versionName="3.0" >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/twofortyfouram_locale_ic_menu_help.xml b/res/drawable/twofortyfouram_locale_ic_menu_help.xml
new file mode 100644
index 0000000..94d3ed3
--- /dev/null
+++ b/res/drawable/twofortyfouram_locale_ic_menu_help.xml
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/drawable/twofortyfouram_locale_ic_menu_save.xml b/res/drawable/twofortyfouram_locale_ic_menu_save.xml
new file mode 100644
index 0000000..bcf17fd
--- /dev/null
+++ b/res/drawable/twofortyfouram_locale_ic_menu_save.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/res/drawable/widget_frame.png b/res/drawable/widget_frame.png
new file mode 100644
index 0000000..45eb8d5
Binary files /dev/null and b/res/drawable/widget_frame.png differ
diff --git a/res/layout/smart_watch_notification_widget.xml b/res/layout/smart_watch_notification_widget.xml
new file mode 100644
index 0000000..7d230b1
--- /dev/null
+++ b/res/layout/smart_watch_notification_widget.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/smart_watch_widget.xml b/res/layout/smart_watch_widget.xml
new file mode 100644
index 0000000..447c67a
--- /dev/null
+++ b/res/layout/smart_watch_widget.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/menu/twofortyfouram_locale_help_save_dontsave.xml b/res/menu/twofortyfouram_locale_help_save_dontsave.xml
new file mode 100644
index 0000000..27fdf71
--- /dev/null
+++ b/res/menu/twofortyfouram_locale_help_save_dontsave.xml
@@ -0,0 +1,47 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000..2efc5dc
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
new file mode 100644
index 0000000..78b4fd4
--- /dev/null
+++ b/res/values-v14/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values-v9/styles.xml b/res/values-v9/styles.xml
new file mode 100644
index 0000000..c85b505
--- /dev/null
+++ b/res/values-v9/styles.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/sony_addon_sdk_headset_pro.xml b/res/values/sony_addon_sdk_headset_pro.xml
new file mode 100644
index 0000000..c568c58
--- /dev/null
+++ b/res/values/sony_addon_sdk_headset_pro.xml
@@ -0,0 +1,7 @@
+
+
+
+ 14px
+ 128px
+ 36px
+
diff --git a/res/values/sony_addon_sdk_smart_watch.xml b/res/values/sony_addon_sdk_smart_watch.xml
new file mode 100644
index 0000000..52d6a14
--- /dev/null
+++ b/res/values/sony_addon_sdk_smart_watch.xml
@@ -0,0 +1,29 @@
+
+
+
+ 128px
+ 128px
+
+ 128px
+ 110px
+ 92px
+ 92px
+
+ @dimen/smart_watch_widget_width_inner
+ 60px
+
+ 14px
+ 12px
+
+ @dimen/smart_watch_text_size_normal
+ @dimen/smart_watch_text_size_small
+ @dimen/smart_watch_text_size_small
+
+
+ 14px
+
+ #ffe6e6e6
+ #fff0832d
+ #ffffffff
+
+
diff --git a/res/values/sony_addon_sdk_smart_watch_2.xml b/res/values/sony_addon_sdk_smart_watch_2.xml
new file mode 100644
index 0000000..0de4ed9
--- /dev/null
+++ b/res/values/sony_addon_sdk_smart_watch_2.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ 220px
+ 176px
+
+ 28px
+
+ 204px
+ 60px
+
+ 20px
+ 15px
+ 13px
+
+ #ffe6e6e6
+ #fff0832d
+ #ffffffff
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4e2783c..d538027 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -85,4 +85,34 @@
This can\'t be blank
Case\nInsensitive
+ Clear All Events
+ "All events on device will be cleared, continue?"
+ Use App Icons
+ Use the apps icon instead of notification icon for watch notifications
+ Launch Configuration App
+ Select which notifications to show and other notification options
+ preference_key_clear
+ preference_key_app_icon
+ preference_key_launch_app
+ Settings
+ All events have been cleared
+ Failed to clear events
+ New notification from
+ Smart Watch Notifier
+ Notifier Source
+
+
+ %1$s%2$s%3$s
+
+
+ \u0020>\u0020
+
+
+ Cancel
+
+
+ Help
+
+
+ Done
\ No newline at end of file
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..c329d5c
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/twofortyfouram_locale_id.xml b/res/values/twofortyfouram_locale_id.xml
new file mode 100644
index 0000000..b093316
--- /dev/null
+++ b/res/values/twofortyfouram_locale_id.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 7a762d7..6735ea6 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -57,6 +57,20 @@
android:title="@string/pref_dnd_time_after"
android:defaultValue="21:00" />
+
+
+
+
+
+
diff --git a/res/xml/sony_preferences.xml b/res/xml/sony_preferences.xml
new file mode 100644
index 0000000..a81f591
--- /dev/null
+++ b/res/xml/sony_preferences.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/dattasmoon/pebble/plugin/AbstractPluginActivity.java b/src/com/dattasmoon/pebble/plugin/AbstractPluginActivity.java
index 2d62f8a..099ba08 100644
--- a/src/com/dattasmoon/pebble/plugin/AbstractPluginActivity.java
+++ b/src/com/dattasmoon/pebble/plugin/AbstractPluginActivity.java
@@ -90,7 +90,6 @@ public boolean onCreateOptionsMenu(final Menu menu) {
return true;
}
return false;
-
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
diff --git a/src/com/dattasmoon/pebble/plugin/Constants.java b/src/com/dattasmoon/pebble/plugin/Constants.java
index f30b264..903a25e 100644
--- a/src/com/dattasmoon/pebble/plugin/Constants.java
+++ b/src/com/dattasmoon/pebble/plugin/Constants.java
@@ -50,6 +50,7 @@ public final class Constants {
public static final String PREFERENCE_CONVERTS = "pref_converts";
public static final String PREFERENCE_IGNORE = "pref_ignore";
public static final String PREFERENCE_PKG_RENAMES = "pref_pkg_renames";
+ public static final String PREFERENCE_APP_ICONS = "preference_key_app_icon";
// Intents
public static final String INTENT_SEND_PEBBLE_NOTIFICATION = "com.getpebble.action.SEND_NOTIFICATION";
@@ -60,6 +61,12 @@ public final class Constants {
// Accessibility specific items
public static final String ACCESSIBILITY_SERVICE = "com.dattasmoon.pebble.plugin/com.dattasmoon.pebble.plugin.NotificationService";
+ // Sony Specific Items
+ public static final String EXTENSION_SPECIFIC_ID = "COM_DATTASMOON_SONY_PLUGIN_SONY_EXTENSION_ID";
+ public static final String EXTENSION_KEY = "com.dattasmoon.sony.plugin.key";
+ public static final String INTENT_ACTION_ADD = "com.dattasmoon.sony.plugin.notification";
+ public static final int DIALOG_CLEAR = 2;
+
public static enum Type {
NOTIFICATION, SETTINGS
};
diff --git a/src/com/dattasmoon/pebble/plugin/EditNotificationActivity.java b/src/com/dattasmoon/pebble/plugin/EditNotificationActivity.java
index 17ca9ef..0026682 100644
--- a/src/com/dattasmoon/pebble/plugin/EditNotificationActivity.java
+++ b/src/com/dattasmoon/pebble/plugin/EditNotificationActivity.java
@@ -16,6 +16,10 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import java.util.Comparator;
import java.util.List;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -30,19 +34,30 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.preference.PreferenceManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
-import android.view.*;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
import android.view.View.OnClickListener;
-import android.widget.*;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
public class EditNotificationActivity extends AbstractPluginActivity {
@@ -88,14 +103,14 @@ public void onNothingSelected(AdapterView> parent) {
}
}
});
-
}
@Override
- public void onResume(){
+ public void onResume() {
super.onResume();
if (mode == Mode.STANDARD) {
- sharedPreferences = getSharedPreferences(Constants.LOG_TAG+"_preferences", MODE_MULTI_PROCESS | MODE_PRIVATE);
+ sharedPreferences = getSharedPreferences(Constants.LOG_TAG + "_preferences", MODE_MULTI_PROCESS
+ | MODE_PRIVATE);
if (sharedPreferences.getBoolean(Constants.PREFERENCE_TASKER_SET, false)) {
tvTaskerNotice.setVisibility(View.VISIBLE);
}
@@ -126,7 +141,7 @@ public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.activity_edit_notifications, menu);
- if(mode == Mode.LOCALE){
+ if (mode == Mode.LOCALE) {
menu.removeItem(R.id.btnSettings);
}
return true;
@@ -150,9 +165,11 @@ public boolean onOptionsItemSelected(MenuItem item) {
builder.setMessage(getString(R.string.dialog_uncheck_message));
builder.setCancelable(false);
builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int buttonId) {
- if (lvPackages == null || lvPackages.getAdapter() == null || ((packageAdapter) lvPackages.getAdapter()).selected == null){
- //something went wrong
+ if (lvPackages == null || lvPackages.getAdapter() == null
+ || ((packageAdapter) lvPackages.getAdapter()).selected == null) {
+ // something went wrong
return;
}
((packageAdapter) lvPackages.getAdapter()).selected.clear();
@@ -160,8 +177,9 @@ public void onClick(DialogInterface dialog, int buttonId) {
}
});
builder.setNegativeButton(R.string.decline, new DialogInterface.OnClickListener() {
+ @Override
public void onClick(DialogInterface dialog, int buttonId) {
- //do nothing!
+ // do nothing!
}
});
builder.setIcon(android.R.drawable.ic_dialog_alert);
@@ -181,18 +199,18 @@ public void onClick(DialogInterface dialog, int buttonId) {
startActivity(settings);
return true;
case R.id.btnRename:
- contextInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
+ contextInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
int position = contextInfo.position;
long id = contextInfo.id;
// the child view who's info we're viewing (should be equal to v)
v = contextInfo.targetView;
- app_name = ((TextView)v.findViewById(R.id.tvPackage)).getText().toString();
+ app_name = ((TextView) v.findViewById(R.id.tvPackage)).getText().toString();
viewHolder = (ListViewHolder) v.getTag();
- if (viewHolder == null || viewHolder.chkEnabled == null){
- //failure
+ if (viewHolder == null || viewHolder.chkEnabled == null) {
+ // failure
return true;
}
- package_name = (String)viewHolder.chkEnabled.getTag();
+ package_name = (String) viewHolder.chkEnabled.getTag();
builder.setTitle(R.string.dialog_title_rename_notification);
final EditText input = new EditText(this);
input.setHint(app_name);
@@ -201,7 +219,7 @@ public void onClick(DialogInterface dialog, int buttonId) {
builder.setNegativeButton(R.string.decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- //do nothing
+ // do nothing
}
});
final AlertDialog d = builder.create();
@@ -213,10 +231,11 @@ public void onShow(DialogInterface dialog) {
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
- //can't be nothing
- if(input.getText().length() > 0){
- if(Constants.IS_LOGGABLE){
- Log.i(Constants.LOG_TAG, "Adding rename for "+ package_name + " to " + input.getText());
+ // can't be nothing
+ if (input.getText().length() > 0) {
+ if (Constants.IS_LOGGABLE) {
+ Log.i(Constants.LOG_TAG,
+ "Adding rename for " + package_name + " to " + input.getText());
}
JSONObject rename = new JSONObject();
try {
@@ -226,7 +245,7 @@ public void onClick(View v) {
} catch (JSONException e) {
e.printStackTrace();
}
- ((packageAdapter)lvPackages.getAdapter()).notifyDataSetChanged();
+ ((packageAdapter) lvPackages.getAdapter()).notifyDataSetChanged();
d.dismiss();
} else {
@@ -243,47 +262,47 @@ public void onClick(View v) {
return true;
case R.id.btnRemoveRename:
- contextInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
+ contextInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
// the child view who's info we're viewing (should be equal to v)
v = contextInfo.targetView;
- app_name = ((TextView)v.findViewById(R.id.tvPackage)).getText().toString();
+ app_name = ((TextView) v.findViewById(R.id.tvPackage)).getText().toString();
viewHolder = (ListViewHolder) v.getTag();
- if (viewHolder == null || viewHolder.chkEnabled == null){
- if(Constants.IS_LOGGABLE){
+ if (viewHolder == null || viewHolder.chkEnabled == null) {
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Viewholder is null or chkEnabled is null");
}
- //failure
+ // failure
return true;
}
- package_name = (String)viewHolder.chkEnabled.getTag();
+ package_name = (String) viewHolder.chkEnabled.getTag();
builder.setTitle(getString(R.string.dialog_title_remove_rename) + app_name + " (" + package_name + ")?");
- builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener(){
+ builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Before remove is: " + String.valueOf(arrayRenames.length()));
}
JSONArray tmp = new JSONArray();
- try{
- for(int i = 0; i < arrayRenames.length(); i++){
- if(!arrayRenames.getJSONObject(i).getString("pkg").equalsIgnoreCase(package_name)){
+ try {
+ for (int i = 0; i < arrayRenames.length(); i++) {
+ if (!arrayRenames.getJSONObject(i).getString("pkg").equalsIgnoreCase(package_name)) {
tmp.put(arrayRenames.getJSONObject(i));
}
}
- } catch (JSONException e){
+ } catch (JSONException e) {
e.printStackTrace();
}
arrayRenames = tmp;
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "After remove is: " + String.valueOf(arrayRenames.length()));
}
- ((packageAdapter)lvPackages.getAdapter()).notifyDataSetChanged();
+ ((packageAdapter) lvPackages.getAdapter()).notifyDataSetChanged();
}
});
builder.setNegativeButton(R.string.decline, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- //do nothing
+ // do nothing
}
});
builder.show();
@@ -459,17 +478,18 @@ public void finish() {
private class LoadAppsTask extends AsyncTask {
public ArrayList selected;
- List pkgAppsList;
- List appsList;
- JSONArray jsonRenames;
+ List pkgAppsList;
+ List appsList;
+ JSONArray jsonRenames;
@Override
- protected void onPreExecute(){
+ protected void onPreExecute() {
PackageManager pm = getPackageManager();
- try{
+ try {
pkgAppsList = pm.getInstalledPackages(0);
- } catch (RuntimeException e){
- //this is usually thrown when people have too many things installed (or bloatware in the case of Samsung devices)
+ } catch (RuntimeException e) {
+ // this is usually thrown when people have too many things
+ // installed (or bloatware in the case of Samsung devices)
pm = getPackageManager();
appsList = pm.getInstalledApplications(0);
}
@@ -479,12 +499,12 @@ protected void onPreExecute(){
@Override
protected Void doInBackground(Void... unused) {
if (pkgAppsList == null && appsList == null) {
- //something went really bad here
+ // something went really bad here
return null;
}
if (appsList == null) {
appsList = new ArrayList();
- for(PackageInfo pkg : pkgAppsList){
+ for (PackageInfo pkg : pkgAppsList) {
appsList.add(pkg.applicationInfo);
}
}
@@ -494,7 +514,7 @@ protected Void doInBackground(Void... unused) {
String packageList;
String packageRenames;
if (mode == Mode.LOCALE) {
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Locale mode");
}
if (localeBundle != null) {
@@ -504,28 +524,28 @@ protected Void doInBackground(Void... unused) {
// this can be null if it doesn't currently exist in the
// locale bundle, handle gracefully
packageList = "";
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Package list from locale bundle is currently null");
}
}
- if (packageRenames == null){
+ if (packageRenames == null) {
packageRenames = "[]";
}
} else {
packageList = "";
packageRenames = "[]";
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Locale bundle is null");
}
}
} else {
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "I am pulling from sharedPrefs");
}
packageList = sharedPreferences.getString(Constants.PREFERENCE_PACKAGE_LIST, "");
packageRenames = sharedPreferences.getString(Constants.PREFERENCE_PKG_RENAMES, "[]");
}
- if(Constants.IS_LOGGABLE){
+ if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Package list is: " + packageList);
}
for (String strPackage : packageList.split(",")) {
@@ -537,9 +557,9 @@ protected Void doInBackground(Void... unused) {
}
}
}
- try{
+ try {
jsonRenames = new JSONArray(packageRenames);
- } catch (JSONException e){
+ } catch (JSONException e) {
e.printStackTrace();
}
return null;
@@ -548,10 +568,10 @@ protected Void doInBackground(Void... unused) {
@Override
protected void onPostExecute(Void unused) {
if (appsList == null) {
- //something went wrong
+ // something went wrong
return;
}
- if(jsonRenames == null){
+ if (jsonRenames == null) {
arrayRenames = new JSONArray();
} else {
arrayRenames = jsonRenames;
@@ -566,12 +586,13 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context
AdapterView.AdapterContextMenuInfo contextInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
int position = contextInfo.position;
long id = contextInfo.id;
- // the child view who's info we're viewing (should be equal to v)
+ // the child view who's info we're viewing (should
+ // be equal to v)
View v = contextInfo.targetView;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list_application_menu, menu);
ListViewHolder viewHolder = (ListViewHolder) v.getTag();
- if(viewHolder.renamed){
+ if (viewHolder.renamed) {
menu.findItem(R.id.btnRename).setVisible(false);
menu.findItem(R.id.btnRemoveRename).setVisible(true);
}
@@ -582,11 +603,12 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context
}
}
- private class packageAdapter extends ArrayAdapter implements OnCheckedChangeListener, OnClickListener {
- private final Context context;
- private final PackageManager pm;
+ private class packageAdapter extends ArrayAdapter implements OnCheckedChangeListener,
+ OnClickListener {
+ private final Context context;
+ private final PackageManager pm;
private final ApplicationInfo[] packages;
- public ArrayList selected;
+ public ArrayList selected;
public packageAdapter(Context context, ApplicationInfo[] packages, ArrayList selected) {
super(context, R.layout.list_application_item, packages);
@@ -610,9 +632,9 @@ public View getView(int position, View rowView, ViewGroup parent) {
viewHolder.chkEnabled = (CheckBox) rowView.findViewById(R.id.chkEnabled);
viewHolder.chkEnabled.setOnCheckedChangeListener(this);
-
rowView.setOnClickListener(this);
- //really wish we didn't have to do this, but if we don't the rowview will gobble this event up.
+ // really wish we didn't have to do this, but if we don't the
+ // rowview will gobble this event up.
rowView.setOnCreateContextMenuListener(null);
rowView.setTag(viewHolder);
} else {
@@ -623,24 +645,24 @@ public View getView(int position, View rowView, ViewGroup parent) {
String appName = null;
viewHolder.renamed = false;
try {
- for(int i = 0; i < arrayRenames.length(); i++){
- if(arrayRenames.getJSONObject(i).getString("pkg").equalsIgnoreCase(info.packageName)){
+ for (int i = 0; i < arrayRenames.length(); i++) {
+ if (arrayRenames.getJSONObject(i).getString("pkg").equalsIgnoreCase(info.packageName)) {
viewHolder.renamed = true;
appName = arrayRenames.getJSONObject(i).getString("to");
viewHolder.textView.setTag(appName);
break;
}
}
- if(!viewHolder.renamed){
+ if (!viewHolder.renamed) {
appName = info.loadLabel(pm).toString();
}
- } catch (NullPointerException e ){
+ } catch (NullPointerException e) {
appName = null;
- } catch (JSONException e){
+ } catch (JSONException e) {
appName = null;
}
- if(appName != null){
+ if (appName != null) {
viewHolder.textView.setText(appName);
} else {
viewHolder.textView.setText("");
@@ -648,10 +670,10 @@ public View getView(int position, View rowView, ViewGroup parent) {
Drawable icon;
try {
icon = info.loadIcon(pm);
- } catch (NullPointerException e){
+ } catch (NullPointerException e) {
icon = null;
}
- if(icon != null){
+ if (icon != null) {
viewHolder.imageView.setImageDrawable(icon);
}
viewHolder.chkEnabled.setTag(info.packageName);
@@ -705,18 +727,20 @@ public void onClick(View rowView) {
}
public static class ListViewHolder {
- public boolean renamed;
+ public boolean renamed;
public TextView textView;
public CheckBox chkEnabled;
public ImageView imageView;
- public ListViewHolder(){
+
+ public ListViewHolder() {
renamed = false;
}
}
public class AppComparator implements Comparator {
final PackageManager pm;
- public AppComparator(Context context){
+
+ public AppComparator(Context context) {
this.pm = context.getPackageManager();
}
diff --git a/src/com/dattasmoon/pebble/plugin/FireReceiver.java b/src/com/dattasmoon/pebble/plugin/FireReceiver.java
index 5eab0c0..b0029b0 100644
--- a/src/com/dattasmoon/pebble/plugin/FireReceiver.java
+++ b/src/com/dattasmoon/pebble/plugin/FireReceiver.java
@@ -31,6 +31,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import com.dattasmoon.pebble.plugin.Constants.Mode;
import com.dattasmoon.pebble.plugin.Constants.Type;
+import com.dattasmoon.sony.plugin.SonyExtensionService;
public class FireReceiver extends BroadcastReceiver {
@@ -59,21 +60,23 @@ public void onReceive(final Context context, final Intent intent) {
break;
}
- //handle quiet hours DND
+ // handle quiet hours DND
boolean quiet_hours = sharedPref.getBoolean(Constants.PREFERENCE_QUIET_HOURS, false);
- //we only need to pull this if quiet hours are enabled. Save the cycles for the cpu! (haha)
- if(quiet_hours){
+ // we only need to pull this if quiet hours are enabled. Save
+ // the cycles for the cpu! (haha)
+ if (quiet_hours) {
String[] pieces = sharedPref.getString(Constants.PREFERENCE_QUIET_HOURS_BEFORE, "00:00").split(":");
- Date quiet_hours_before= new Date(0, 0, 0, Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1]));
+ Date quiet_hours_before = new Date(0, 0, 0, Integer.parseInt(pieces[0]),
+ Integer.parseInt(pieces[1]));
pieces = sharedPref.getString(Constants.PREFERENCE_QUIET_HOURS_AFTER, "23:59").split(":");
Date quiet_hours_after = new Date(0, 0, 0, Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1]));
Calendar c = Calendar.getInstance();
Date now = new Date(0, 0, 0, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
if (Constants.IS_LOGGABLE) {
- Log.i(Constants.LOG_TAG, "Checking quiet hours. Now: " + now.toString() + " vs " +
- quiet_hours_before.toString() + " and " +quiet_hours_after.toString());
+ Log.i(Constants.LOG_TAG, "Checking quiet hours. Now: " + now.toString() + " vs "
+ + quiet_hours_before.toString() + " and " + quiet_hours_after.toString());
}
- if(now.before(quiet_hours_before) || now.after(quiet_hours_after)){
+ if (now.before(quiet_hours_before) || now.after(quiet_hours_after)) {
if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Time is before or after the quiet hours time. Returning.");
}
@@ -85,6 +88,7 @@ public void onReceive(final Context context, final Intent intent) {
String body = intent.getStringExtra(Constants.BUNDLE_EXTRA_STRING_BODY);
sendAlertToPebble(context, bundleVersionCode, title, body);
+ sendAlertToSmartWatch(context, bundleVersionCode, title, body);
break;
case SETTINGS:
Mode mode = Mode.values()[intent.getIntExtra(Constants.BUNDLE_EXTRA_INT_MODE, Mode.OFF.ordinal())];
@@ -96,7 +100,7 @@ public void onReceive(final Context context, final Intent intent) {
}
}
- public void setNotificationSettings(final Context context, int bundleVersionCode, Mode mode,String packageList) {
+ public void setNotificationSettings(final Context context, int bundleVersionCode, Mode mode, String packageList) {
if (Constants.IS_LOGGABLE) {
switch (mode) {
@@ -115,7 +119,8 @@ public void setNotificationSettings(final Context context, int bundleVersionCode
Log.i(Constants.LOG_TAG, "Package list is: " + packageList);
}
- Editor editor = context.getSharedPreferences(Constants.LOG_TAG+"_preferences", context.MODE_MULTI_PROCESS | context.MODE_PRIVATE).edit();
+ Editor editor = context.getSharedPreferences(Constants.LOG_TAG + "_preferences",
+ context.MODE_MULTI_PROCESS | context.MODE_PRIVATE).edit();
editor.putInt(Constants.PREFERENCE_MODE, mode.ordinal());
editor.putBoolean(Constants.PREFERENCE_TASKER_SET, true);
editor.putString(Constants.PREFERENCE_PACKAGE_LIST, packageList);
@@ -160,4 +165,21 @@ public void sendAlertToPebble(final Context context, int bundleVersionCode, Stri
}
context.sendBroadcast(i);
}
+
+ public void sendAlertToSmartWatch(final Context context, int bundleVersionCode, String title, String body) {
+ // Create the intent to house the Pebble notification
+ final Intent i = new Intent(context, SonyExtensionService.class);
+ i.setAction(Constants.INTENT_ACTION_ADD);
+ i.putExtra("sender", context.getString(R.string.app_name));
+ i.putExtra("title", title);
+ i.putExtra("body", body);
+
+ // Send the alert to Pebble
+ if (Constants.IS_LOGGABLE) {
+ Log.d(Constants.LOG_TAG, "About to send a modal alert to Smart Watch: " + title + ":" + body);
+ }
+ context.startService(i); // // Use startService instead of sendBroadcast
+ // so the notification gets passed to SW2
+ // properly
+ }
}
diff --git a/src/com/dattasmoon/pebble/plugin/NotificationService.java b/src/com/dattasmoon/pebble/plugin/NotificationService.java
index 4889a74..b253a25 100644
--- a/src/com/dattasmoon/pebble/plugin/NotificationService.java
+++ b/src/com/dattasmoon/pebble/plugin/NotificationService.java
@@ -12,7 +12,12 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import java.io.File;
import java.io.IOException;
-import java.util.*;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -24,11 +29,15 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.TargetApi;
import android.app.Notification;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Parcelable;
@@ -43,6 +52,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
import android.widget.TextView;
import com.dattasmoon.pebble.plugin.Constants.Mode;
+import com.dattasmoon.sony.plugin.SonyExtensionService;
public class NotificationService extends AccessibilityService {
private class queueItem {
@@ -55,24 +65,25 @@ public queueItem(String title, String body) {
}
}
- private Mode mode = Mode.EXCLUDE;
- private boolean notifications_only = false;
- private boolean no_ongoing_notifs = false;
- private boolean notification_extras = false;
- private boolean quiet_hours = false;
- private boolean notifScreenOn = true;
- private JSONArray converts = new JSONArray();
- private JSONArray ignores = new JSONArray();
- private JSONArray pkg_renames = new JSONArray();
- private long min_notification_wait = 0 * 1000;
- private long notification_last_sent = 0;
- private Date quiet_hours_before = null;
- private Date quiet_hours_after = null;
- private String[] packages = null;
- private Handler mHandler;
- private File watchFile;
- private Long lastChange;
- Queue queue;
+ private Mode mode = Mode.EXCLUDE;
+ private boolean notifications_only = false;
+ private boolean no_ongoing_notifs = false;
+ private boolean notification_extras = false;
+ private boolean useAppIcons = false;
+ private boolean quiet_hours = false;
+ private boolean notifScreenOn = true;
+ private JSONArray converts = new JSONArray();
+ private JSONArray ignores = new JSONArray();
+ private JSONArray pkg_renames = new JSONArray();
+ private long min_notification_wait = 0 * 1000;
+ private long notification_last_sent = 0;
+ private Date quiet_hours_before = null;
+ private Date quiet_hours_after = null;
+ private String[] packages = null;
+ private Handler mHandler;
+ private File watchFile;
+ private Long lastChange;
+ Queue queue;
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
@@ -92,25 +103,26 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
return;
}
- //handle quiet hours
- if(quiet_hours){
+ // handle quiet hours
+ if (quiet_hours) {
Calendar c = Calendar.getInstance();
Date now = new Date(0, 0, 0, c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
if (Constants.IS_LOGGABLE) {
- Log.i(Constants.LOG_TAG, "Checking quiet hours. Now: " + now.toString() + " vs " +
- quiet_hours_before.toString() + " and " +quiet_hours_after.toString());
+ Log.i(Constants.LOG_TAG,
+ "Checking quiet hours. Now: " + now.toString() + " vs " + quiet_hours_before.toString()
+ + " and " + quiet_hours_after.toString());
}
- if(quiet_hours_before.after(quiet_hours_after)){
- if(now.after(quiet_hours_after) && now.before(quiet_hours_before)){
+ if (quiet_hours_before.after(quiet_hours_after)) {
+ if (now.after(quiet_hours_after) && now.before(quiet_hours_before)) {
if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Time is during quiet time. Returning.");
}
return;
}
- } else if(now.before(quiet_hours_before) || now.after(quiet_hours_after)){
+ } else if (now.before(quiet_hours_before) || now.after(quiet_hours_after)) {
if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, "Time is before or after the quiet hours time. Returning.");
}
@@ -133,11 +145,11 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
}
}
}
- if (no_ongoing_notifs){
+ if (no_ongoing_notifs) {
Parcelable parcelable = event.getParcelableData();
if (parcelable instanceof Notification) {
Notification notif = (Notification) parcelable;
- if ((notif.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT){
+ if ((notif.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG,
"Event is a notification, notification flag contains ongoing, and no ongoing notification is true. Returning.");
@@ -146,13 +158,11 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
}
} else {
if (Constants.IS_LOGGABLE) {
- Log.i(Constants.LOG_TAG,
- "Event is not a notification.");
+ Log.i(Constants.LOG_TAG, "Event is not a notification.");
}
}
}
-
// Handle the do not disturb screen on settings
PowerManager powMan = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
if (Constants.IS_LOGGABLE) {
@@ -177,7 +187,7 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
PackageManager pm = getPackageManager();
String eventPackageName;
- if (event.getPackageName() != null){
+ if (event.getPackageName() != null) {
eventPackageName = event.getPackageName().toString();
} else {
eventPackageName = "";
@@ -230,13 +240,13 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
String title = "";
try {
boolean renamed = false;
- for(int i = 0; i < pkg_renames.length(); i++){
- if(pkg_renames.getJSONObject(i).getString("pkg").equalsIgnoreCase(eventPackageName)){
+ for (int i = 0; i < pkg_renames.length(); i++) {
+ if (pkg_renames.getJSONObject(i).getString("pkg").equalsIgnoreCase(eventPackageName)) {
renamed = true;
title = pkg_renames.getJSONObject(i).getString("to");
}
}
- if(!renamed){
+ if (!renamed) {
title = pm.getApplicationLabel(pm.getApplicationInfo(eventPackageName, 0)).toString();
}
} catch (NameNotFoundException e) {
@@ -245,6 +255,38 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
title = eventPackageName;
}
+ // get the icon for the app that posted the notification
+ String iconURIPath = null;
+
+ try {
+ String mPackageName = event.getPackageName().toString();
+
+ int resId = 0;
+ if (useAppIcons) {
+ ApplicationInfo ai = pm.getApplicationInfo(mPackageName, 0);
+ resId = ai.icon;
+ } else {
+ Notification parcelable = (Notification) event.getParcelableData();
+ resId = parcelable.icon;
+ }
+
+ Context remotePackageContext = getApplicationContext().createPackageContext(mPackageName, 0);
+ Resources resources = remotePackageContext.getResources();
+ String tempURI = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + resources.getResourcePackageName(resId)
+ + '/' + resources.getResourceTypeName(resId) + '/' + resources.getResourceEntryName(resId);
+ Uri iconURI = Uri.parse(tempURI); // Make sure this is a valid URI
+ // before assigning it to the path
+ // we pass on
+
+ iconURIPath = tempURI;
+ if (Constants.IS_LOGGABLE) {
+ Log.i(Constants.LOG_TAG, "Notification icon URI object: " + iconURI);
+ Log.i(Constants.LOG_TAG, "Notification icon URI path: " + iconURIPath);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
// get the notification text
String notificationText = event.getText().toString();
// strip the first and last characters which are [ and ]
@@ -266,52 +308,55 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
}
// Check ignore lists
- for(int i = 0; i < ignores.length(); i++){
- try{
+ for (int i = 0; i < ignores.length(); i++) {
+ try {
JSONObject ignore = ignores.getJSONObject(i);
String app = ignore.getString("app");
boolean exclude = ignore.optBoolean("exclude", true);
boolean case_insensitive = ignore.optBoolean("insensitive", true);
- if((!app.equals("-1")) && (!eventPackageName.equalsIgnoreCase(app))){
- //this rule doesn't apply to all apps and this isn't the app we're looking for.
+ if ((!app.equals("-1")) && (!eventPackageName.equalsIgnoreCase(app))) {
+ // this rule doesn't apply to all apps and this isn't the
+ // app we're looking for.
continue;
}
String regex = "";
- if(case_insensitive){
+ if (case_insensitive) {
regex += "(?i)";
}
- if(!ignore.getBoolean("raw")){
+ if (!ignore.getBoolean("raw")) {
regex += Pattern.quote(ignore.getString("match"));
} else {
regex += ignore.getString("match");
}
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(notificationText);
- if(m.find()){
- if(exclude){
+ if (m.find()) {
+ if (exclude) {
if (Constants.IS_LOGGABLE) {
- Log.i(Constants.LOG_TAG, "Notification text of '" + notificationText + "' matches: '" + regex +"' and exclude is on. Returning");
+ Log.i(Constants.LOG_TAG, "Notification text of '" + notificationText + "' matches: '"
+ + regex + "' and exclude is on. Returning");
}
return;
}
} else {
- if(!exclude){
- if(Constants.IS_LOGGABLE){
- Log.i(Constants.LOG_TAG, "Notification text of '" + notificationText + "' does not match: '" + regex +"' and include is on. Returning");
+ if (!exclude) {
+ if (Constants.IS_LOGGABLE) {
+ Log.i(Constants.LOG_TAG, "Notification text of '" + notificationText
+ + "' does not match: '" + regex + "' and include is on. Returning");
}
return;
}
}
- } catch (JSONException e){
+ } catch (JSONException e) {
continue;
}
}
-
// Send the alert to Pebble
sendToPebble(title, notificationText);
+ sendToSmartWatch(iconURIPath, title, notificationText);
if (Constants.IS_LOGGABLE) {
Log.i(Constants.LOG_TAG, event.toString());
@@ -328,18 +373,19 @@ private void sendToPebble(String title, String notificationText) {
}
return;
}
- for(int i = 0; i < converts.length(); i++){
+ for (int i = 0; i < converts.length(); i++) {
String from;
String to;
- try{
+ try {
JSONObject convert = converts.getJSONObject(i);
from = "(?i)" + Pattern.quote(convert.getString("from"));
to = convert.getString("to");
- } catch (JSONException e){
+ } catch (JSONException e) {
continue;
}
- //not sure if the title should be replaced as well or not. I'm guessing not
- //title = title.replaceAll(from, to);
+ // not sure if the title should be replaced as well or not. I'm
+ // guessing not
+ // title = title.replaceAll(from, to);
notificationText = notificationText.replaceAll(from, to);
}
@@ -367,6 +413,49 @@ private void sendToPebble(String title, String notificationText) {
}
+ private void sendToSmartWatch(String iconURI, String title, String notificationText) {
+ title = title.trim();
+ notificationText = notificationText.trim();
+ if (title.trim().isEmpty() || notificationText.isEmpty()) {
+ if (Constants.IS_LOGGABLE) {
+ Log.i(Constants.LOG_TAG, "Detected empty title or notification text, skipping");
+ }
+ return;
+ }
+ for (int i = 0; i < converts.length(); i++) {
+ String from;
+ String to;
+ try {
+ JSONObject convert = converts.getJSONObject(i);
+ from = "(?i)" + Pattern.quote(convert.getString("from"));
+ to = convert.getString("to");
+ } catch (JSONException e) {
+ continue;
+ }
+ // not sure if the title should be replaced as well or not. I'm
+ // guessing not
+ // title = title.replaceAll(from, to);
+ notificationText = notificationText.replaceAll(from, to);
+ }
+
+ // Create the intent to house the Pebble notification
+ final Intent i = new Intent(this, SonyExtensionService.class);
+ i.setAction(Constants.INTENT_ACTION_ADD);
+ i.putExtra("sender", getString(R.string.app_name));
+ i.putExtra("title", title);
+ i.putExtra("body", notificationText);
+ i.putExtra("iconURI", iconURI);
+
+ // Send the alert to Pebble
+ if (Constants.IS_LOGGABLE) {
+ Log.d(Constants.LOG_TAG, "About to send a modal alert to Smart Watch: " + title + ":" + notificationText);
+ }
+ startService(i); // Use startService instead of sendBroadcast so the
+ // notification gets passed to SW2 properly
+ notification_last_sent = System.currentTimeMillis();
+
+ }
+
@Override
public void onInterrupt() {
@@ -381,7 +470,6 @@ protected void onServiceConnected() {
try {
watchFile.createNewFile();
} catch (IOException e) {
- // TODO Auto-generated catch block
e.printStackTrace();
}
watchFile.setLastModified(System.currentTimeMillis());
@@ -405,16 +493,22 @@ private void loadPrefs() {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences sharedPreferences = getSharedPreferences(Constants.LOG_TAG, MODE_MULTI_PROCESS | MODE_PRIVATE);
- //if old preferences exist, convert them.
- if(sharedPreferences.contains(Constants.LOG_TAG + ".mode")){
+ // if old preferences exist, convert them.
+ if (sharedPreferences.contains(Constants.LOG_TAG + ".mode")) {
SharedPreferences.Editor editor = sharedPref.edit();
- editor.putInt(Constants.PREFERENCE_MODE, sharedPreferences.getInt(Constants.LOG_TAG + ".mode", Constants.Mode.OFF.ordinal()));
- editor.putString(Constants.PREFERENCE_PACKAGE_LIST, sharedPreferences.getString(Constants.LOG_TAG + ".packageList", ""));
- editor.putBoolean(Constants.PREFERENCE_NOTIFICATIONS_ONLY, sharedPreferences.getBoolean(Constants.LOG_TAG + ".notificationsOnly", true));
- editor.putBoolean(Constants.PREFERENCE_NOTIFICATION_EXTRA, sharedPreferences.getBoolean(Constants.LOG_TAG + ".fetchNotificationExtras", false));
+ editor.putInt(Constants.PREFERENCE_MODE,
+ sharedPreferences.getInt(Constants.LOG_TAG + ".mode", Constants.Mode.OFF.ordinal()));
+ editor.putString(Constants.PREFERENCE_PACKAGE_LIST,
+ sharedPreferences.getString(Constants.LOG_TAG + ".packageList", ""));
+ editor.putBoolean(Constants.PREFERENCE_NOTIFICATIONS_ONLY,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + ".notificationsOnly", true));
+ editor.putBoolean(Constants.PREFERENCE_APP_ICONS,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + ".appIcons", false));
+ editor.putBoolean(Constants.PREFERENCE_NOTIFICATION_EXTRA,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + ".fetchNotificationExtras", false));
editor.commit();
- //clear out all old preferences
+ // clear out all old preferences
editor = sharedPreferences.edit();
editor.clear();
editor.commit();
@@ -434,29 +528,31 @@ private void loadPrefs() {
packages = sharedPref.getString(Constants.PREFERENCE_PACKAGE_LIST, "").split(",");
notifications_only = sharedPref.getBoolean(Constants.PREFERENCE_NOTIFICATIONS_ONLY, true);
no_ongoing_notifs = sharedPref.getBoolean(Constants.PREFERENCE_NO_ONGOING_NOTIF, false);
+ useAppIcons = sharedPref.getBoolean(Constants.PREFERENCE_APP_ICONS, false);
min_notification_wait = sharedPref.getInt(Constants.PREFERENCE_MIN_NOTIFICATION_WAIT, 0) * 1000;
notification_extras = sharedPref.getBoolean(Constants.PREFERENCE_NOTIFICATION_EXTRA, false);
notifScreenOn = sharedPref.getBoolean(Constants.PREFERENCE_NOTIF_SCREEN_ON, true);
quiet_hours = sharedPref.getBoolean(Constants.PREFERENCE_QUIET_HOURS, false);
- try{
+ try {
converts = new JSONArray(sharedPref.getString(Constants.PREFERENCE_CONVERTS, "[]"));
- } catch (JSONException e){
+ } catch (JSONException e) {
converts = new JSONArray();
}
- try{
+ try {
ignores = new JSONArray(sharedPref.getString(Constants.PREFERENCE_IGNORE, "[]"));
- } catch (JSONException e){
+ } catch (JSONException e) {
ignores = new JSONArray();
}
- try{
+ try {
pkg_renames = new JSONArray(sharedPref.getString(Constants.PREFERENCE_PKG_RENAMES, "[]"));
- } catch (JSONException e){
+ } catch (JSONException e) {
pkg_renames = new JSONArray();
}
- //we only need to pull this if quiet hours are enabled. Save the cycles for the cpu! (haha)
- if(quiet_hours){
+ // we only need to pull this if quiet hours are enabled. Save the cycles
+ // for the cpu! (haha)
+ if (quiet_hours) {
String[] pieces = sharedPref.getString(Constants.PREFERENCE_QUIET_HOURS_BEFORE, "00:00").split(":");
- quiet_hours_before= new Date(0, 0, 0, Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1]));
+ quiet_hours_before = new Date(0, 0, 0, Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1]));
pieces = sharedPref.getString(Constants.PREFERENCE_QUIET_HOURS_AFTER, "23:59").split(":");
quiet_hours_after = new Date(0, 0, 0, Integer.parseInt(pieces[0]), Integer.parseInt(pieces[1]));
}
@@ -541,7 +637,7 @@ private String dumpViewGroup(int depth, ViewGroup vg, String existing_text) {
if (v instanceof TextView) {
TextView tv = (TextView) v;
- if (tv.getText().toString() == "..." || tv.getText().toString() == "�"
+ if (tv.getText().toString() == "..." || tv.getText().toString() == "���"
|| isInteger(tv.getText().toString())
|| tv.getText().toString().trim().equalsIgnoreCase(existing_text)) {
if (Constants.IS_LOGGABLE) {
diff --git a/src/com/dattasmoon/pebble/plugin/SettingsActivity.java b/src/com/dattasmoon/pebble/plugin/SettingsActivity.java
index 17ff2f8..5110959 100644
--- a/src/com/dattasmoon/pebble/plugin/SettingsActivity.java
+++ b/src/com/dattasmoon/pebble/plugin/SettingsActivity.java
@@ -21,22 +21,30 @@ of this software and associated documentation files (the "Software"), to deal
*/
package com.dattasmoon.pebble.plugin;
+import java.io.File;
+import java.io.IOException;
+
+import android.app.AlertDialog;
import android.app.Dialog;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
+import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
-import java.io.File;
-import java.io.IOException;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;
/**
* This activity handles any logic for the settings screen (of which there is\
@@ -55,17 +63,24 @@ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedPreferences = getSharedPreferences(Constants.LOG_TAG, MODE_MULTI_PROCESS | MODE_PRIVATE);
- //if old preferences exist, convert them.
- if(sharedPreferences.contains(Constants.LOG_TAG + ".mode")){
- SharedPreferences sharedPref = getSharedPreferences(Constants.LOG_TAG + "_preferences", MODE_MULTI_PROCESS | MODE_PRIVATE);
+ // if old preferences exist, convert them.
+ if (sharedPreferences.contains(Constants.LOG_TAG + ".mode")) {
+ SharedPreferences sharedPref = getSharedPreferences(Constants.LOG_TAG + "_preferences", MODE_MULTI_PROCESS
+ | MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
- editor.putInt(Constants.PREFERENCE_MODE, sharedPreferences.getInt(Constants.LOG_TAG + ".mode", Constants.Mode.OFF.ordinal()));
- editor.putString(Constants.PREFERENCE_PACKAGE_LIST, sharedPreferences.getString(Constants.LOG_TAG + ".packageList", ""));
- editor.putBoolean(Constants.PREFERENCE_NOTIFICATIONS_ONLY, sharedPreferences.getBoolean(Constants.LOG_TAG + ".notificationsOnly", true));
- editor.putBoolean(Constants.PREFERENCE_NOTIFICATION_EXTRA, sharedPreferences.getBoolean(Constants.LOG_TAG + ".fetchNotificationExtras", false));
+ editor.putInt(Constants.PREFERENCE_MODE,
+ sharedPreferences.getInt(Constants.LOG_TAG + ".mode", Constants.Mode.OFF.ordinal()));
+ editor.putString(Constants.PREFERENCE_PACKAGE_LIST,
+ sharedPreferences.getString(Constants.LOG_TAG + ".packageList", ""));
+ editor.putBoolean(Constants.PREFERENCE_NOTIFICATIONS_ONLY,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + ".notificationsOnly", true));
+ editor.putBoolean(Constants.PREFERENCE_APP_ICONS,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + "appIcons", false));
+ editor.putBoolean(Constants.PREFERENCE_NOTIFICATION_EXTRA,
+ sharedPreferences.getBoolean(Constants.LOG_TAG + ".fetchNotificationExtras", false));
editor.commit();
- //clear out all old preferences
+ // clear out all old preferences
editor = sharedPreferences.edit();
editor.clear();
editor.commit();
@@ -89,7 +104,7 @@ protected void onCreate(Bundle savedInstanceState) {
@Override
public boolean onPreferenceClick(Preference preference) {
pref_version_clicks++;
- if(pref_version_clicks > 5){
+ if (pref_version_clicks > 5) {
final Dialog easterDialog = new Dialog(SettingsActivity.this);
easterDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
easterDialog.setContentView(getLayoutInflater().inflate(R.layout.dialog_easter_hidden, null));
@@ -104,14 +119,14 @@ public void onClick(View v) {
}
return true;
-
}
});
Preference pref_donate = findPreference("pref_donate");
pref_donate.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
public boolean onPreferenceClick(Preference preference) {
- //send intent
+ // send intent
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(Constants.DONATION_URL));
startActivity(i);
@@ -119,11 +134,25 @@ public boolean onPreferenceClick(Preference preference) {
}
});
+ // Handle clear all events
+ Preference preference = findPreference(getString(R.string.pref_key_clear));
+ preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ showDialog(Constants.DIALOG_CLEAR);
+ return true;
+ }
+ });
+ // Remove preferences that are not supported by the accessory
+ if (!ExtensionUtils.supportsHistory(getIntent())) {
+ preference = findPreference(getString(R.string.pref_key_clear));
+ getPreferenceScreen().removePreference(preference);
+ }
}
@Override
- protected void onPause(){
+ protected void onPause() {
File watchFile = new File(getFilesDir() + "PrefsChanged.none");
if (!watchFile.exists()) {
try {
@@ -137,4 +166,70 @@ protected void onPause(){
super.onPause();
}
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ Dialog dialog = null;
+
+ switch (id) {
+ case Constants.DIALOG_CLEAR:
+ dialog = createClearDialog();
+ break;
+ default:
+ Log.w(Constants.LOG_TAG, "Not a valid dialog id: " + id);
+ break;
+ }
+
+ return dialog;
+ }
+
+ /**
+ * Create the Clear events dialog
+ *
+ * @return the Dialog
+ */
+ private Dialog createClearDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.pref_option_clear_txt).setTitle(R.string.pref_option_clear)
+ .setIcon(android.R.drawable.ic_input_delete)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ new ClearEventsTask().execute();
+ }
+ }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();
+ }
+
+ /**
+ * Clear all messaging events
+ */
+ private class ClearEventsTask extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ int nbrDeleted = 0;
+ nbrDeleted = NotificationUtil.deleteAllEvents(SettingsActivity.this);
+ return nbrDeleted;
+ }
+
+ @Override
+ protected void onPostExecute(Integer id) {
+ if (id != NotificationUtil.INVALID_ID) {
+ Toast.makeText(SettingsActivity.this, R.string.clear_success, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(SettingsActivity.this, R.string.clear_failure, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ }
+
}
diff --git a/src/com/dattasmoon/sony/plugin/SonyExtensionReceiver.java b/src/com/dattasmoon/sony/plugin/SonyExtensionReceiver.java
new file mode 100644
index 0000000..8039ebf
--- /dev/null
+++ b/src/com/dattasmoon/sony/plugin/SonyExtensionReceiver.java
@@ -0,0 +1,31 @@
+/*
+Copyright (c) 2013 Dattas Moonchaser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package com.dattasmoon.sony.plugin;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.dattasmoon.pebble.plugin.Constants;
+
+/**
+ * The extension receiver receives the extension intents and starts the
+ * extension service when it arrives.
+ */
+public class SonyExtensionReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ Log.d(Constants.LOG_TAG, "onReceive: " + intent.getAction());
+ intent.setClass(context, SonyExtensionService.class);
+ context.startService(intent);
+ }
+}
diff --git a/src/com/dattasmoon/sony/plugin/SonyExtensionService.java b/src/com/dattasmoon/sony/plugin/SonyExtensionService.java
new file mode 100644
index 0000000..08e3ab5
--- /dev/null
+++ b/src/com/dattasmoon/sony/plugin/SonyExtensionService.java
@@ -0,0 +1,208 @@
+/*
+Copyright (c) 2013 Dattas Moonchaser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.dattasmoon.sony.plugin;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.SQLException;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.dattasmoon.pebble.plugin.Constants;
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionService;
+import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;
+import com.sonyericsson.extras.liveware.extension.util.registration.RegistrationInformation;
+
+/**
+ * The sample extension service handles extension registration and inserts data
+ * into the notification database.
+ */
+public class SonyExtensionService extends ExtensionService {
+
+ public SonyExtensionService() {
+ super(Constants.EXTENSION_KEY);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see android.app.Service#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(Constants.LOG_TAG, "onCreate");
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see android.app.Service#onStartCommand()
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ int retVal = super.onStartCommand(intent, flags, startId);
+ if (intent != null && Constants.INTENT_ACTION_ADD.equals(intent.getAction())) {
+ Log.d(Constants.LOG_TAG, "onStart action: INTENT_ACTION_ADD");
+ Bundle extras = intent.getExtras();
+ String iconURI = null;
+ if (extras.containsKey("iconURI")) {
+ iconURI = (String) extras.get("iconURI");
+ }
+ addData(iconURI, (String) extras.get("title"), (String) extras.get("body"));
+ stopSelfCheck();
+ }
+
+ return retVal;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(Constants.LOG_TAG, "onDestroy");
+ }
+
+ /**
+ * Add some "random" data
+ */
+ private void addData(String imageURI, String name, String message) {
+ long time = System.currentTimeMillis();
+ long sourceId = NotificationUtil.getSourceId(this, Constants.EXTENSION_SPECIFIC_ID);
+ if (sourceId == NotificationUtil.INVALID_ID) {
+ Log.e(Constants.LOG_TAG, "Failed to insert data");
+ return;
+ }
+ ContentValues eventValues = new ContentValues();
+ eventValues.put(Notification.EventColumns.EVENT_READ_STATUS, false);
+ eventValues.put(Notification.EventColumns.DISPLAY_NAME, name);
+ eventValues.put(Notification.EventColumns.MESSAGE, message);
+ eventValues.put(Notification.EventColumns.PERSONAL, 1);
+ eventValues.put(Notification.EventColumns.PUBLISHED_TIME, time);
+ eventValues.put(Notification.EventColumns.SOURCE_ID, sourceId);
+ if (imageURI != null) {
+ eventValues.put(Notification.EventColumns.IMAGE_URI, imageURI);
+ }
+
+ try {
+ getContentResolver().insert(Notification.Event.URI, eventValues);
+ } catch (IllegalArgumentException e) {
+ Log.e(Constants.LOG_TAG, "Failed to insert event", e);
+ } catch (SecurityException e) {
+ Log.e(Constants.LOG_TAG, "Failed to insert event, is Live Ware Manager installed?", e);
+ } catch (SQLException e) {
+ Log.e(Constants.LOG_TAG, "Failed to insert event", e);
+ }
+ }
+
+ @Override
+ protected void onViewEvent(Intent intent) {
+ // String action =
+ // intent.getStringExtra(Notification.Intents.EXTRA_ACTION);
+ // String hostAppPackageName = intent
+ // .getStringExtra(Registration.Intents.EXTRA_AHA_PACKAGE_NAME);
+ // boolean advancedFeaturesSupported =
+ // DeviceInfoHelper.isSmartWatch2ApiAndScreenDetected(
+ // this, hostAppPackageName);
+ //
+ // int eventId = intent.getIntExtra(Notification.Intents.EXTRA_EVENT_ID,
+ // -1);
+ // if (Notification.SourceColumns.ACTION_1.equals(action)) {
+ // doAction1(eventId);
+ // } else if (Notification.SourceColumns.ACTION_2.equals(action)) {
+ // // Here we can take different actions depending on the device.
+ // if (advancedFeaturesSupported) {
+ // Toast.makeText(this, "Action 2 API level 2",
+ // Toast.LENGTH_LONG).show();
+ // } else {
+ // Toast.makeText(this, "Action 2", Toast.LENGTH_LONG).show();
+ // }
+ // } else if (Notification.SourceColumns.ACTION_3.equals(action)) {
+ // Toast.makeText(this, "Action 3", Toast.LENGTH_LONG).show();
+ // }
+ }
+
+ @Override
+ protected void onRefreshRequest() {
+ // Do nothing here, only relevant for polling extensions, this
+ // extension is always up to date
+ }
+
+ // /**
+ // * Show toast with event information
+ // *
+ // * @param eventId The event id
+ // */
+ // public void doAction1(int eventId) {
+ // Log.d(LOG_TAG, "doAction1 event id: " + eventId);
+ // Cursor cursor = null;
+ // try {
+ // String name = "";
+ // String message = "";
+ // cursor = getContentResolver().query(Notification.Event.URI, null,
+ // Notification.EventColumns._ID + " = " + eventId, null, null);
+ // if (cursor != null && cursor.moveToFirst()) {
+ // int nameIndex =
+ // cursor.getColumnIndex(Notification.EventColumns.DISPLAY_NAME);
+ // int messageIndex =
+ // cursor.getColumnIndex(Notification.EventColumns.MESSAGE);
+ // name = cursor.getString(nameIndex);
+ // message = cursor.getString(messageIndex);
+ // }
+ //
+ // String toastMessage = getText(R.string.action_event_1) + ", Event: " +
+ // eventId
+ // + ", Name: " + name + ", Message: " + message;
+ // Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show();
+ // } catch (SQLException e) {
+ // Log.e(LOG_TAG, "Failed to query event", e);
+ // } catch (SecurityException e) {
+ // Log.e(LOG_TAG, "Failed to query event", e);
+ // } catch (IllegalArgumentException e) {
+ // Log.e(LOG_TAG, "Failed to query event", e);
+ // } finally {
+ // if (cursor != null) {
+ // cursor.close();
+ // }
+ // }
+ // }
+
+ /**
+ * Called when extension and sources has been successfully registered.
+ * Override this method to take action after a successful registration.
+ */
+ @Override
+ public void onRegisterResult(boolean result) {
+ super.onRegisterResult(result);
+ Log.d(Constants.LOG_TAG, "onRegisterResult");
+ }
+
+ @Override
+ protected RegistrationInformation getRegistrationInformation() {
+ return new SonyRegistrationInformation(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.aef.util.ExtensionService#
+ * keepRunningWhenConnected()
+ */
+ @Override
+ protected boolean keepRunningWhenConnected() {
+ return false;
+ }
+}
diff --git a/src/com/dattasmoon/sony/plugin/SonyPreferenceActivity.java b/src/com/dattasmoon/sony/plugin/SonyPreferenceActivity.java
new file mode 100644
index 0000000..280ab2a
--- /dev/null
+++ b/src/com/dattasmoon/sony/plugin/SonyPreferenceActivity.java
@@ -0,0 +1,141 @@
+/*
+Copyright (c) 2013 Dattas Moonchaser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.dattasmoon.sony.plugin;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.dattasmoon.pebble.plugin.Constants;
+import com.dattasmoon.pebble.plugin.R;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;
+
+/**
+ * Create the few preferences needed for the Sony preference activity that is
+ * launched via Sony Smart Connect
+ */
+public class SonyPreferenceActivity extends PreferenceActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Add the minimal set of preferences specific to Sony Smart Connect
+ addPreferencesFromResource(R.xml.sony_preferences);
+
+ // Add a preference to launch the main app for primary configuration
+ Preference preference = new Preference(this);
+ preference.setTitle(R.string.pref_option_launch_app);
+ preference.setSummary(R.string.pref_option_launch_app_txt);
+ PreferenceCategory pc = (PreferenceCategory) findPreference("Sony SW2");
+ pc.addPreference(preference);
+ preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent LaunchIntent = getPackageManager().getLaunchIntentForPackage("com.dattasmoon.pebble.plugin");
+ startActivity(LaunchIntent);
+ return true;
+ }
+ });
+
+ // Handle clear all events
+ preference = findPreference(getString(R.string.pref_key_clear));
+ preference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ showDialog(Constants.DIALOG_CLEAR);
+ return true;
+ }
+ });
+
+ // Remove preferences that are not supported by the accessory
+ if (!ExtensionUtils.supportsHistory(getIntent())) {
+ preference = findPreference(getString(R.string.pref_key_clear));
+ getPreferenceScreen().removePreference(preference);
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ Dialog dialog = null;
+
+ switch (id) {
+ case Constants.DIALOG_CLEAR:
+ dialog = createClearDialog();
+ break;
+ default:
+ Log.w(Constants.LOG_TAG, "Not a valid dialog id: " + id);
+ break;
+ }
+
+ return dialog;
+ }
+
+ /**
+ * Create the Clear events dialog
+ *
+ * @return the Dialog
+ */
+ private Dialog createClearDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.pref_option_clear_txt).setTitle(R.string.pref_option_clear)
+ .setIcon(android.R.drawable.ic_input_delete)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ new ClearEventsTask().execute();
+ }
+ }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();
+ }
+
+ /**
+ * Clear all messaging events
+ */
+ private class ClearEventsTask extends AsyncTask {
+
+ @Override
+ protected void onPreExecute() {
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ int nbrDeleted = 0;
+ nbrDeleted = NotificationUtil.deleteAllEvents(SonyPreferenceActivity.this);
+ return nbrDeleted;
+ }
+
+ @Override
+ protected void onPostExecute(Integer id) {
+ if (id != NotificationUtil.INVALID_ID) {
+ Toast.makeText(SonyPreferenceActivity.this, R.string.clear_success, Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(SonyPreferenceActivity.this, R.string.clear_failure, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ }
+}
diff --git a/src/com/dattasmoon/sony/plugin/SonyRegistrationInformation.java b/src/com/dattasmoon/sony/plugin/SonyRegistrationInformation.java
new file mode 100644
index 0000000..ea859f7
--- /dev/null
+++ b/src/com/dattasmoon/sony/plugin/SonyRegistrationInformation.java
@@ -0,0 +1,131 @@
+/*
+Copyright (c) 2013 Dattas Moonchaser
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package com.dattasmoon.sony.plugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.ContentValues;
+import android.content.Context;
+
+import com.dattasmoon.pebble.plugin.Constants;
+import com.dattasmoon.pebble.plugin.R;
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+import com.sonyericsson.extras.liveware.extension.util.registration.RegistrationInformation;
+
+public class SonyRegistrationInformation extends RegistrationInformation {
+
+ final Context mContext;
+
+ /**
+ * Create notification registration object
+ *
+ * @param context
+ * The context
+ */
+ protected SonyRegistrationInformation(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ mContext = context;
+ }
+
+ @Override
+ public int getRequiredNotificationApiVersion() {
+ return 1;
+ }
+
+ @Override
+ public int getRequiredWidgetApiVersion() {
+ return 0;
+ }
+
+ @Override
+ public int getRequiredControlApiVersion() {
+ return 0;
+ }
+
+ @Override
+ public int getRequiredSensorApiVersion() {
+ return 0;
+ }
+
+ @Override
+ public ContentValues getExtensionRegistrationConfiguration() {
+ String extensionIcon = ExtensionUtils.getUriString(mContext, R.drawable.icon_extension);
+ String iconHostapp = ExtensionUtils.getUriString(mContext, R.drawable.ic_launcher);
+ String extensionIcon48 = ExtensionUtils.getUriString(mContext, R.drawable.icon_extension_48);
+
+ String configurationText = mContext.getString(R.string.pref_sony_activity_title);
+ String extensionName = mContext.getString(R.string.extension_name);
+
+ ContentValues values = new ContentValues();
+ values.put(Registration.ExtensionColumns.CONFIGURATION_ACTIVITY, SonyPreferenceActivity.class.getName());
+ values.put(Registration.ExtensionColumns.CONFIGURATION_TEXT, configurationText);
+ values.put(Registration.ExtensionColumns.EXTENSION_ICON_URI, extensionIcon);
+ values.put(Registration.ExtensionColumns.EXTENSION_48PX_ICON_URI, extensionIcon48);
+
+ values.put(Registration.ExtensionColumns.EXTENSION_KEY, Constants.EXTENSION_KEY);
+ values.put(Registration.ExtensionColumns.HOST_APP_ICON_URI, iconHostapp);
+ values.put(Registration.ExtensionColumns.NAME, extensionName);
+ values.put(Registration.ExtensionColumns.NOTIFICATION_API_VERSION, getRequiredNotificationApiVersion());
+ values.put(Registration.ExtensionColumns.PACKAGE_NAME, mContext.getPackageName());
+
+ return values;
+ }
+
+ @Override
+ public ContentValues[] getSourceRegistrationConfigurations() {
+ List bulkValues = new ArrayList();
+ bulkValues.add(getSourceRegistrationConfiguration(Constants.EXTENSION_SPECIFIC_ID));
+ return bulkValues.toArray(new ContentValues[bulkValues.size()]);
+ }
+
+ /**
+ * Get source configuration associated with extensions specific id
+ *
+ * @param extensionSpecificId
+ * @return The source configuration
+ */
+ public ContentValues getSourceRegistrationConfiguration(String extensionSpecificId) {
+ ContentValues sourceValues = null;
+
+ String iconSource1 = ExtensionUtils.getUriString(mContext, R.drawable.icn_30x30_message_notification);
+ String iconSource2 = ExtensionUtils.getUriString(mContext, R.drawable.icn_18x18_message_notification);
+ String iconBw = ExtensionUtils.getUriString(mContext, R.drawable.icn_18x18_black_white_message_notification);
+ String textToSpeech = mContext.getString(R.string.text_to_speech);
+ sourceValues = new ContentValues();
+ sourceValues.put(Notification.SourceColumns.ENABLED, true);
+ sourceValues.put(Notification.SourceColumns.ICON_URI_1, iconSource1);
+ sourceValues.put(Notification.SourceColumns.ICON_URI_2, iconSource2);
+ sourceValues.put(Notification.SourceColumns.ICON_URI_BLACK_WHITE, iconBw);
+ sourceValues.put(Notification.SourceColumns.UPDATE_TIME, System.currentTimeMillis());
+ sourceValues.put(Notification.SourceColumns.NAME, mContext.getString(R.string.source_name));
+ sourceValues.put(Notification.SourceColumns.EXTENSION_SPECIFIC_ID, extensionSpecificId);
+ sourceValues.put(Notification.SourceColumns.PACKAGE_NAME, mContext.getPackageName());
+ sourceValues.put(Notification.SourceColumns.TEXT_TO_SPEECH, textToSpeech);
+ // sourceValues.put(Notification.SourceColumns.ACTION_1,
+ // mContext.getString(R.string.action_event_1));
+ // sourceValues.put(Notification.SourceColumns.ACTION_2,
+ // mContext.getString(R.string.action_event_2));
+ // sourceValues.put(Notification.SourceColumns.ACTION_3,
+ // mContext.getString(R.string.action_event_3));
+ // sourceValues.put(Notification.SourceColumns.ACTION_ICON_1,
+ // ExtensionUtils.getUriString(mContext, R.drawable.actions_1));
+ // sourceValues.put(Notification.SourceColumns.ACTION_ICON_2,
+ // ExtensionUtils.getUriString(mContext, R.drawable.actions_2));
+ // sourceValues.put(Notification.SourceColumns.ACTION_ICON_3,
+ // ExtensionUtils.getUriString(mContext, R.drawable.actions_3));
+ return sourceValues;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/aef/control/Control.java b/src/com/sonyericsson/extras/liveware/aef/control/Control.java
new file mode 100644
index 0000000..d5e3943
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/control/Control.java
@@ -0,0 +1,1719 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (C) 2012-2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.aef.control;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+
+
+/**
+ * Control API is a part of the Smart Extension APIs
+ *
+ * Some of our Smart accessories will support the Control API.
+ * The Control API enables the Extension to take total control of the accessory.
+ * It takes control over the display, LEDs, vibrator, input events.
+ * Because of this, only one Extension can run in this mode at a time.
+ *
+ * Topics covered here:
+ *
+ * - Registration
+ *
- Extension lifecycle
+ *
- Controlling the display
+ *
- Controlling the LEDs
+ *
- Controlling the vibrator
+ *
- Key events
+ *
- Touch events
+ *
- Displaying content on the accessory display
+ *
+ *
+ *
+ * Registration
+ *
+ * Before a Control Extension can use an accessory, it must use the registration API
+ * content provider to insert a record in the extension table. It must also register
+ * information in the registration table. This must be done for each Host Application
+ * that the Extension wants to interact with.
+ *
+ *
+ * In order to find out what Host Applications are available and what capabilities they
+ * support, the Extension should use the Capability API.
+ *
+ *
+ * Extension lifecycle
+ *
+ * After a successful registration the Extension can start communicating with the Host
+ * Application. Since an Extension implementing this API takes complete control over the
+ * accessory only one Extension can run at a time.
+ *
+ *
+ * An Extension cannot just start executing whenever it wants, it needs to make sure that
+ * no other Extension is running, therefore the Extension can only request to be started,
+ * {@link Intents#CONTROL_START_REQUEST_INTENT}. When the Host Application is ready to give
+ * control to the Extension it will send a {@link Intents#CONTROL_START_INTENT}, see figure
+ * below.
+ *
+ *
+ *
+ *
+ *
+ * When the Extension requests to start controlling the accessory the Host Application can
+ * either accept the request and give control to the Extension, or if something is not right
+ * the Host Application can send a {@link Intents#CONTROL_ERROR_INTENT}. See
+ * {@link Intents#EXTRA_ERROR_CODE} for different error codes that the Host Application can send.
+ *
+ *
+ * The {@link Intents#CONTROL_RESUME_INTENT} is sent when the Extension is visible on the accessory.
+ * From this point on the Extension controls everything, the Host Application just forwards the
+ * information between the accessory and the Extension.
+ *
+ *
+ * An Extension can also be paused, either if a high priority Extension needs to run for a
+ * while or if the Host Application is in charge of the display state and the display is
+ * turned off. In this case the Host Application sends a {@link Intents#CONTROL_PAUSE_INTENT}
+ * to the Extension. This means that there is no point for the Extension to update the display
+ * since it is either turned off or someone else has control over it. If the Extension would
+ * break this rule and try to update the display anyway, the Host Application will ignore these
+ * calls.
+ *
+ *
+ * When the Extension is in a paused state it no longer has control over the display/LEDs/
+ * vibrator/key events. As an example one could say that a telephony Extension has high priority.
+ * E.g. when a random Extension is running and the user receives a phone call. We want to pause
+ * the running Extension and let the telephony Extension display the caller id on the accessory
+ * display. When the phone call ends the telephony Extension is done and the other Extension can
+ * resume its running, it will then receive a {@link Intents#CONTROL_RESUME_INTENT}.
+ *
+ *
+ * When the {@link Intents#CONTROL_RESUME_INTENT} is sent from a Host Application the Extension is
+ * once again in charge of everything.
+ *
+ *
+ * When the user chooses to exit the Extension the Host Application will send a
+ * {@link Intents#CONTROL_PAUSE_INTENT} followed by a {@link Intents#CONTROL_STOP_INTENT}.
+ * From this point on the Host Application regains control.
+ *
+ *
+ * If the Extension would like to stop itself when running, like the telephony Extension, it can
+ * send a {@link Intents#CONTROL_STOP_REQUEST_INTENT} to the Host Application. The Host Application
+ * will then make sure to stop it and send a {@link Intents#CONTROL_STOP_INTENT}.
+ * If the extension was not already paused the it will be paused before it is stopped and a
+ * {@link Intents#CONTROL_PAUSE_INTENT} is sent before the {@link Intents#CONTROL_STOP_INTENT}.
+ * In case another Extension has been paused it will be resumed.
+ *
+ *
+ *
+ * Controlling the display
+ *
+ * Extensions implementing this API have the possibility to control the state of the accessory
+ * display.The display can be controlled via {@link Intents#CONTROL_SET_SCREEN_STATE_INTENT}.
+ *
+ *
+ * It is important that you program your Extension so that it consumes as little power as possible,
+ * both on the phone side and on the accessory. The accessory has a much smaller battery then the
+ * phone so use this functionality with caution. When possible, let the Host Application take control
+ * of the display state. That way you don't have to bother about the power consumption on the accessory.
+ * You can do this by setting the display state to "Auto".
+ *
+ *
+ * By default when your Extension starts the display state will be set to "Auto", which means that the
+ * Host Application controls the on/off/dim behavior. If the Extension wants to control the display state
+ * it must explicitly change the state.
+ *
+ *
+ * If the Extension controls the display state and you get a {@link Intents#CONTROL_STOP_INTENT}, meaning
+ * your Extension is no longer running, the Host Application will automatically take over the display
+ * control.
+ *
+ *
+ * Note that when in "Auto" mode, the Extension will receive a {@link Intents#CONTROL_PAUSE_INTENT} when
+ * display is off and a {@link Intents#CONTROL_RESUME_INTENT} when the display goes back on.
+ *
+ * Active power save mode
+ *
+ * Some accessories may support an additional display mode where information can be shown to the
+ * user while keeping the battery consumption to a minimum. In this active power save mode the
+ * display only supports monochrome (black and white) display content.
+ * Accessories with support for active power save mode indicates this in
+ * {@link Registration.DisplayColumns#SUPPORTS_LOW_POWER_MODE}.
+ * If the control extension wants use the active power save mode it must set the
+ * {@link Registration.ApiRegistration#LOW_POWER_SUPPORT} to TRUE when registering with a Host
+ * Application.
+ *
+ *
+ * If both the extension and the accessory support active power save mode, this mode will be
+ * activated when the screen would otherwise go off.
+ * This means that if screen state is {@link Intents#SCREEN_STATE_AUTO} the accessory will decide when to
+ * enter active power save mode.
+ * If screen state is {@link Intents#SCREEN_STATE_ON} or {@link Intents#SCREEN_STATE_DIM} the
+ * extension can put the display in active power save mode by setting the screen state to
+ * {@link Intents#SCREEN_STATE_OFF}.
+ * The {@link Intents#CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT} intent is sent to the
+ * extension when the display enters active power save mode both when the active power save
+ * mode is initiated by the accessory and extension.
+ * When in active power save mode the extension is expected to provide monochrome display
+ * content through the same intents as in normal display mode.
+ * The extension can update the display in the same ways as in normal display mode.
+ *
+ *
+ * If the screen state is {@link Intents#SCREEN_STATE_AUTO} and the display was put in active
+ * power save mode by the accessory, the accessory also decides when to leave the active
+ * power save mode.
+ * In this mode the extension will not receive any input events from the accessory as these
+ * will cause the display to leave the active power save mode.
+ * If the display was put in active power save mode by the control extension it is the
+ * responsibility of the control extension to decide when to leave the active power save mode
+ * by setting the screen state to {@link Intents#SCREEN_STATE_ON}.
+ * If the extension wants to get input events when the screen is in active power save mode, it must
+ * manually put the display in active power save mode.
+ * When the display leaves the active power save mode a
+ * {@link Intents#CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT} will always be sent to
+ * the extension and the extension is expected by update the display with new content.
+ *
+ *
+ * Controlling the LEDs
+ *
+ * The accessory might have one or more LEDs that are used to notify the user about events. The
+ * Extension can find information about the LEDs for a certain accessory via the Registration &
+ * Capability API.
+ *
+ *
+ * If the accessory has LEDs, the Extension can control them via the Control API. The LEDs can be
+ * controlled via the {@link Intents#CONTROL_LED_INTENT}.
+ * Note that the Host Application might overtake the control of the LED at any time if it wants to
+ * show some important notifications to the user, e.g. when the accessory battery level is low.
+ * The Extension is unaware of this so it might still try to control the LEDs but the Host
+ * Application will ignore the calls.
+ *
+ *
+ * Controlling the vibrator
+ *
+ * Our accessories might or might not have a vibrator. The Extension can find this out by checking
+ * the capabilities of the Host Application via the Registration & Capability API. If the accessory
+ * has a vibrator it is controllable via the Control API, {@link Intents#CONTROL_VIBRATE_INTENT}.
+ *
+ *
+ * Key events
+ *
+ * The accessory might have several hardware keys. Your extension will receive the key events when
+ * one of the keys is pressed. The {@link Intents#CONTROL_KEY_EVENT_INTENT} is sent to the Extension when
+ * a user presses a key on the accessory.
+ *
+ *
+ * The Intent carries a few parameters, such as the time stamp of the event, the type of event
+ * (press, release and repeat) and also the key code. The accessory might have one or more keypads
+ * defined. Extensions can look this up in the Registration & Capabilities API. Each key will have a
+ * unique key code for identification. Key codes can be found in the product SDK.
+ *
+ *
+ * Touch events
+ *
+ * Certain accessories might have a touch display. Extensions can find this information using the
+ * Registration & Capabilities API. The {@link Intents#CONTROL_TOUCH_EVENT_INTENT} is sent to the
+ * Extension when a user taps the accessory display.
+ *
+ *
+ * If the {@link Intents#CONTROL_DISPLAY_DATA_INTENT} is used to send images, then touch events with
+ * display coordinates are delivered in the {@link Intents#CONTROL_TOUCH_EVENT_INTENT} intents.
+ * If a swipe gesture is detected then a {@link Intents#CONTROL_SWIPE_EVENT_INTENT} is sent to the
+ * Extension instead.
+ *
+ *
+ * If the {@link Intents#CONTROL_PROCESS_LAYOUT_INTENT} is used to send layouts then some Views
+ * in the layout may handle the touch events themselves.
+ * Touch events are for example handled by views that have android:clickable set to to true.
+ * For these views the extension is informed about clicks through the
+ * {@link Intents#CONTROL_OBJECT_CLICK_EVENT_INTENT} intent.
+ * ListViews also handle touch event and report clicks in {@link Intents#CONTROL_LIST_ITEM_CLICK_INTENT}
+ * intents.
+ * Touch events and swipe gestures that are not handled by Views in the layout are sent to the
+ * Extension through {@link Intents#CONTROL_TOUCH_EVENT_INTENT} and
+ * {@link Intents#CONTROL_SWIPE_EVENT_INTENT} intents.
+ *
+ *
+ * Displaying content on the accessory display
+ *
+ * Since the Extension is controlling the accessory it also controls what is visible on the display.
+ * The content visible to the user comes from the Extension. Basically the Extension sends images to
+ * be displayed on the accessory display. To find out the dimensions of the display and the color depth
+ * it supports the Extension can use the Registration & Capabilities API. The
+ * {@link Intents#CONTROL_DISPLAY_DATA_INTENT} is sent from the Extension when it wants to update the accessory
+ * display. Extensions can also clear the accessory display at any point if they want to by sending
+ * the {@link Intents#CONTROL_CLEAR_DISPLAY_INTENT}.
+ *
+ *
+ * The Extension can send images as raw data (byte array) or it can just send the URI of the image to
+ * be displayed. Note that we are using Bluetooth as bearer which means that we can't send that many
+ * frames per second (FPS). Refresh rate of the display can be found in the Registration & Capabilities API.
+ *
+ *
+ * Layouts
+ *
+ * Starting with version 2 of the Control API it is possible to send layouts to the accessory as an
+ * alternative to sending images.
+ * The subset of Android layouts that are supported is specified in {@link Intents#EXTRA_DATA_XML_LAYOUT}.
+ * Layouts are sent using {@link Intents#CONTROL_PROCESS_LAYOUT_INTENT}.
+ * The contents of the views in the layouts can be updated using {@link Intents#CONTROL_SEND_IMAGE_INTENT}
+ * and {@link Intents#CONTROL_SEND_TEXT_INTENT}.
+ * When using layouts, click events are delivered as {@link Intents#CONTROL_OBJECT_CLICK_EVENT_INTENT} intents.
+ *
+ *
+ * Lists
+ *
+ * A layout may include a ListView.
+ * The ListView is initiated by sending a {@link Intents#CONTROL_LIST_COUNT_INTENT}.
+ * This intent can include the list items in the {@link Intents#EXTRA_LIST_CONTENT}.
+ * If no {@link Intents#EXTRA_LIST_CONTENT} is provided the Host Application will request
+ * individual list items when needed through {@link Intents#CONTROL_LIST_REQUEST_ITEM_INTENT}.
+ * The Control extension can refresh the list content at any time by sending a new
+ * {@link Intents#CONTROL_LIST_COUNT_INTENT} intent.
+ * This can be done both if additional items should be added or just if the existing items
+ * should be refreshed.
+ * The Control extension is notified about clicks on list items through the
+ * {@link Intents#CONTROL_LIST_ITEM_CLICK_INTENT} intent.
+ *
+ *
+ * A ListView and its items must always fill the entire display width.
+ * The height of a list item must be less or equal to the height of the ListView.
+ *
+ *
+ * Some lists may support a user initiated refresh.
+ * The {@link Intents#CONTROL_LIST_REFRESH_REQUEST_INTENT} is sent to the extension if
+ * the user performs a manual refresh action.
+ * The extension is expected to check its data source (for example trigger a poll to a server)
+ * and update the list content.
+ * If the number of items is changed a new {@link Intents#CONTROL_LIST_COUNT_INTENT} should be sent.
+ * If only the existing list items should be updated this is done through a number of
+ * {@link Intents#CONTROL_LIST_ITEM_INTENT} intents.
+ *
+ *
+ * Gallery
+ *
+ * The Control extension interacts with the Gallery view in the same way as the ListView with the
+ * addition that it is also notified about selected list items through the
+ * {@link Intents#CONTROL_LIST_ITEM_SELECTED_INTENT} intent.
+ *
+ *
+ * A Gallery and its items must always fill the entire display width.
+ * The height of a list item must be less or equal to the height of the Gallery.
+ *
+ */
+
+public class Control {
+
+ /**
+ * @hide
+ * This class is only intended as a utility class containing declared constants
+ * that will be used by Control API Extension developers.
+ */
+ protected Control() {
+ }
+
+ /**
+ * Intents sent between Control Extensions and Accessory Host Applications.
+ */
+ public interface Intents {
+
+ /**
+ * Intent sent by the Extension when it wants to take control of the accessory display.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_START_REQUEST_INTENT = "com.sonyericsson.extras.aef.control.START_REQUEST";
+
+ /**
+ * Intent sent by the Extension when it wants to stop controlling the accessory display.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_STOP_REQUEST_INTENT = "com.sonyericsson.extras.aef.control.STOP_REQUEST";
+
+ /**
+ * Intent sent by the Host Application when it grants control of the accessory display to the Extension.
+ * This Intent might be sent when the Host Application wants to start the Extension or as a
+ * result of the Extensions sending a {@link #CONTROL_START_REQUEST_INTENT} Intent.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_START_INTENT = "com.sonyericsson.extras.aef.control.START";
+
+ /**
+ * Intent sent by the Host Application when it takes back control of the accessory display from the Extension.
+ * This Intent might be sent when the Host Application wants to stop the Extension or as a
+ * result of the Extensions sending a {@link #CONTROL_STOP_REQUEST_INTENT} Intent.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_STOP_INTENT = "com.sonyericsson.extras.aef.control.STOP";
+
+ /**
+ * Intent sent by the Host Application when the Extension is no longer visible on the display.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_PAUSE_INTENT = "com.sonyericsson.extras.aef.control.PAUSE";
+
+ /**
+ * Intent sent by the Host Application when the Extension is visible on the display.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_RESUME_INTENT = "com.sonyericsson.extras.aef.control.RESUME";
+
+ /**
+ * Intent sent by the Host Application when a error occurs
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_ERROR_CODE}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_ERROR_INTENT = "com.sonyericsson.extras.aef.control.ERROR";
+
+ /**
+ * Intent sent by the Extension when it wants to set the state of the accessory display.
+ * If the Extension does not set the state explicitly it will by default be controlled by
+ * the Host Application (Display Auto) for optimal power consumption.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_SCREEN_STATE}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_SET_SCREEN_STATE_INTENT = "com.sonyericsson.extras.aef.control.SET_SCREEN_STATE";
+
+ /**
+ * Intent sent by the Extension when it wants to control one of the LEDs available on the accessory.
+ * Every Host Application will expose information about its LEDs in the
+ * Registration & Capabilities API.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LED_ID}
+ * - {@link #EXTRA_LED_COLOR}
+ * - {@link #EXTRA_ON_DURATION}
+ * - {@link #EXTRA_OFF_DURATION}
+ * - {@link #EXTRA_REPEATS}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_LED_INTENT = "com.sonyericsson.extras.aef.control.LED";
+
+ /**
+ * Intent sent by the Extension when it wants to stop an ongoing LED sequence on the accessory.
+ * If the LED specified in the Intent-extra if off, this Intent will be ignored by the Host Application.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LED_ID}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_STOP_LED_INTENT = "com.sonyericsson.extras.aef.control.STOP_LED";
+
+ /**
+ * Intent sent by the Extension when it wants to control the vibrator available on the accessory.
+ * Every Host Application will expose information about the vibrator if it has one in the
+ * Registration & Capabilities API.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_ON_DURATION}
+ * - {@link #EXTRA_OFF_DURATION}
+ * - {@link #EXTRA_REPEATS}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_VIBRATE_INTENT = "com.sonyericsson.extras.aef.control.VIBRATE";
+
+ /**
+ * Intent sent by the Extension when it wants to stop an ongoing vibration on the accessory.
+ * If no vibration is ongoing, this Intent will be ignored by the Host Application.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_STOP_VIBRATE_INTENT = "com.sonyericsson.extras.aef.control.STOP_VIBRATE";
+
+ /**
+ * Intent sent by the Extension whenever it wants to update the accessory display.
+ * The display size is accessory dependent and can be found using the Registration & Capabilities API.
+ *
+ * The Accessory may support several displays.
+ * The displays can be real displays or emulated displays to provide compatibility for
+ * Extensions written for other Accessories.
+ * If several displays are supported the Extension can use {@link #EXTRA_DISPLAY_ID}
+ * to specify the target display.
+ * If {@link #EXTRA_DISPLAY_ID} is absent the Host Application will make a selection
+ * of target display and if necessary scale the image.
+ *
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_DATA_URI}
+ * - {@link #EXTRA_DATA}
+ * - {@link #EXTRA_X_OFFSET}
+ * - {@link #EXTRA_Y_OFFSET}
+ * - {@link #EXTRA_DISPLAY_ID} (optional)
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_DISPLAY_DATA_INTENT = "com.sonyericsson.extras.aef.control.DISPLAY_DATA";
+
+ /**
+ * Intent sent by the Extension whenever it wants to clear the accessory display.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_CLEAR_DISPLAY_INTENT = "com.sonyericsson.extras.aef.control.CLEAR_DISPLAY";
+
+ /**
+ * Intent sent by the Host Application to the controlling Extension whenever an hardware
+ * key is pressed/released.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_KEY_ACTION}
+ * - {@link #EXTRA_TIMESTAMP}
+ * - {@link #EXTRA_KEY_CODE}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_KEY_EVENT_INTENT = "com.sonyericsson.extras.aef.control.KEY_EVENT";
+
+ /**
+ * Intent sent by the Host Application to the controlling Extension whenever an touch
+ * event is detected.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_TOUCH_ACTION}
+ * - {@link #EXTRA_TIMESTAMP}
+ * - {@link #EXTRA_X_POS}
+ * - {@link #EXTRA_Y_POS}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_TOUCH_EVENT_INTENT = "com.sonyericsson.extras.aef.control.TOUCH_EVENT";
+
+ /**
+ * Intent sent by the Host Application to the controlling Extension whenever an swipe
+ * event is detected.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_SWIPE_DIRECTION}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_SWIPE_EVENT_INTENT = "com.sonyericsson.extras.aef.control.SWIPE_EVENT";
+
+ /**
+ * Intent sent by the Extension whenever it wants to update the Accessory display by sending an XML layout.
+ * Note that the extension must always send a layout covering the full screen according to the display screen size.
+ * This means that even though the extension wants to update a portion of the screen a full screen layout must
+ * be supplied anyway. Objects that should be displayed in the layout are sent separately.
+ *
+ * {@link #EXTRA_LAYOUT_DATA} can be used to update view in the layout with new values.
+ * The content of the view in the layout can also be updated using {@link #CONTROL_SEND_TEXT_INTENT} and {@link #CONTROL_SEND_IMAGE_INTENT}.
+ *
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_DATA_XML_LAYOUT}
+ * - {@link #EXTRA_LAYOUT_DATA} (optional)
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_PROCESS_LAYOUT_INTENT = "com.sonyericsson.extras.aef.control.PROCESS_LAYOUT";
+
+ /**
+ * Intent sent by the Control extension whenever it wants to update an image in an ImageView on the accessory.
+ * The image can be a URI or an array of a raw image, like JPEG
+ * The image will replace any previous sent image with the same reference.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_DATA_URI}
+ * - {@link #EXTRA_DATA}
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_SEND_IMAGE_INTENT = "com.sonyericsson.extras.aef.control.SEND_IMAGE";
+
+ /**
+ * Intent sent by the Control extension whenever it wants to update a text in a TextView on the accessory.
+ * The text will replace any previous sent text with the same reference.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_TEXT}
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_SEND_TEXT_INTENT = "com.sonyericsson.extras.aef.control.SEND_TEXT";
+
+ /**
+ * Intent sent by the Host Application to the controlling Extension whenever the active power save mode status changed.
+ * This intent is only sent to control extensions that support active power save mode.
+ * In active power save mode, the control should supply a black and white image.
+ *
+ * Depending on the mode, a suitable layout should be sent to the accessory.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_ACTIVE_POWER_MODE_STATUS}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT = "com.sonyericsson.extras.aef.control.ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED";
+
+ /**
+ * Intent sent by the Host Application to the controlling Extension whenever a click
+ * event is detected on a graphical object referenced from a layout.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_CLICK_TYPE}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_OBJECT_CLICK_EVENT_INTENT = "com.sonyericsson.extras.aef.control.OBJECT_CLICK_EVENT";
+
+ /**
+ * Intent sent by the Control extension when it wants to set the number
+ * of items in a ListView. {@link #EXTRA_LAYOUT_REFERENCE} refers to the
+ * ListView to be updated. This intent is used both to specify the
+ * initial size of the list and to update the size of the list.
+ *
+ * {@link #EXTRA_LIST_REFRESH_ALLOWED} specifies if the user is allowed
+ * to manually initiate a refresh of the list content. The default
+ * behavior is that the user is not allowed to initiate a refresh.
+ * The extension is notified about a refresh request through the
+ * {@link #CONTROL_LIST_REFRESH_REQUEST_INTENT} intent.
+ *
+ *
+ * This intent should be sent with enforced security by supplying the
+ * host application permission to sendBroadcast(Intent, String).
+ * {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_LIST_REFRESH_ALLOWED} (optional)
+ * - {@link #EXTRA_LIST_COUNT}
+ * - {@link #EXTRA_LIST_CONTENT} (optional)
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_COUNT_INTENT = "com.sonyericsson.extras.aef.control.LIST_COUNT";
+
+
+ /**
+ * Intent sent by the Control extension when it wants to move to a
+ * certain position in a list. The position to move to can either be
+ * specified using {@link #EXTRA_LIST_ITEM_POSITION} or
+ * {@link #EXTRA_LIST_ITEM_ID}.
+ *
+ * This intent should be sent with enforced security by supplying the
+ * host application permission to sendBroadcast(Intent, String).
+ * {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_LIST_ITEM_ID} or {@link #EXTRA_LIST_ITEM_POSITION}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_MOVE_INTENT = "com.sonyericsson.extras.aef.control.LIST_MOVE";
+
+ /**
+ * Intent sent the by the Host Application to the Control extension as a
+ * request for a list item.
+ * The extension is expected to respond with a {@link #CONTROL_LIST_ITEM_INTENT}.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_LIST_ITEM_POSITION}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_REQUEST_ITEM_INTENT = "com.sonyericsson.extras.aef.control.LIST_REQUEST_ITEM";
+
+ /**
+ * Intent sent by the Control extension to update a list item in a
+ * ListView. This can be a response to
+ * {@link #CONTROL_LIST_REQUEST_ITEM_INTENT}, but it can also be sent
+ * unsolicited to refresh an individual list item. To refresh an entire
+ * list a new {@link #CONTROL_LIST_COUNT_INTENT} can be used.
+ *
+ * {@link #EXTRA_LAYOUT_REFERENCE} specifies the ListView and
+ * {@link #EXTRA_LIST_ITEM_POSITION} specifies the position to update.
+ * {@link #EXTRA_DATA_XML_LAYOUT} specifies the layout of the list item.
+ * {@link #EXTRA_LAYOUT_DATA} can be used to update views in the list
+ * item layout with new values.
+ *
+ *
+ * This intent should be sent with enforced security by supplying the
+ * host application permission to sendBroadcast(Intent, String).
+ * {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_DATA_XML_LAYOUT}
+ * - {@link #EXTRA_LIST_ITEM_ID}
+ * - {@link #EXTRA_LIST_ITEM_POSITION}
+ * - {@link #EXTRA_LAYOUT_DATA} (optional)
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_ITEM_INTENT = "com.sonyericsson.extras.aef.control.LIST_ITEM";
+
+ /**
+ * Intent sent by the Host Application to the Control extension when a
+ * list item has been clicked. If the list item contains any views where
+ * android:clickable is true and one of these views were clicked the
+ * android:id of that view is returned in
+ * {@link #EXTRA_LIST_ITEM_LAYOUT_REFERENCE}.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_LIST_ITEM_ID}
+ * - {@link #EXTRA_LIST_ITEM_POSITION}
+ * - {@link #EXTRA_CLICK_TYPE}
+ * - {@link #EXTRA_LIST_ITEM_LAYOUT_REFERENCE} (optional)
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_ITEM_CLICK_INTENT = "com.sonyericsson.extras.aef.control.LIST_ITEM_CLICK";
+
+ /**
+ * Intent sent by the Host Application to the Control extension when a
+ * list item is selected.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_LIST_ITEM_ID}
+ * - {@link #EXTRA_LIST_ITEM_POSITION}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_ITEM_SELECTED_INTENT = "com.sonyericsson.extras.aef.control.LIST_ITEM_SELECTED";
+
+ /**
+ * Intent sent by the Host Application to the Control extension when a
+ * list refresh request is detected.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_LIST_REFRESH_REQUEST_INTENT = "com.sonyericsson.extras.aef.control.LIST_REFERESH_REQUEST";
+
+ /**
+ * Intent sent by the Control extension when it wants to show a menu.
+ * The {@link #CONTROL_MENU_ITEM_SELECTED} intent is sent to the Control extension when a
+ * menu item has been selected.
+ *
+ *
+ * This intent should be sent with enforced security by supplying the
+ * host application permission to sendBroadcast(Intent, String).
+ * {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_MENU_ITEMS}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_MENU_SHOW = "com.sonyericsson.extras.aef.control.MENU_SHOW";
+
+ /**
+ * Intent sent by the Host Application to the Control extension when a menu item has been
+ * selected.
+ * {@link #EXTRA_MENU_ITEM_ID} identifies the selected menu item.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ * - {@link #EXTRA_MENU_ITEM_ID}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_MENU_ITEM_SELECTED = "com.sonyericsson.extras.aef.control.MENU_ITEM_SELECTED";
+
+ /**
+ * The name of the Intent-extra used to identify the Host Application.
+ * The Host Application will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AHA_PACKAGE_NAME = "aha_package_name";
+
+ /**
+ * The name of the Intent-extra used to identify the Extension.
+ * The Extension will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AEA_PACKAGE_NAME = "aea_package_name";
+
+ /**
+ * The name of the Intent-extra carrying the state of the display
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #SCREEN_STATE_OFF}
+ * - {@link #SCREEN_STATE_DIM}
+ * - {@link #SCREEN_STATE_ON}
+ * - {@link #SCREEN_STATE_AUTO}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SCREEN_STATE = "screen_state";
+
+ /**
+ * The name of the Intent-extra carrying the ID of the LED to be controlled
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_LED_ID = "led_id";
+
+ /**
+ * The name of the Intent-extra carrying the color you want the LED to blink with
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_LED_COLOR = "led_color";
+
+ /**
+ * The name of the Intent-extra carrying the "on" duration in milliseconds
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ON_DURATION = "on_duration";
+
+ /**
+ * The name of the Intent-extra carrying the "off" duration in milliseconds
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_OFF_DURATION = "off_duration";
+
+ /**
+ * The name of the Intent-extra carrying the number of repeats of the on/off pattern.
+ * Note, the value {@link #REPEAT_UNTIL_STOP_INTENT} means that the on/off pattern is repeated until
+ * the {@link #CONTROL_STOP_VIBRATE_INTENT} or {@link #CONTROL_STOP_LED_INTENT} intent is received
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_REPEATS = "repeats";
+
+ /**
+ * The name of the Intent-extra used to identify the URI of the image to be displayed on the
+ * accessory display. If the image is in raw data (e.g. an array of bytes) use
+ * {@link #EXTRA_DATA} instead
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_DATA_URI = "data_uri";
+
+ /**
+ * The name of the Intent-extra used to identify the data to be displayed on the accessory
+ * display. This Intent-extra should be used if the image is in raw data (e.g. an array of bytes)
+ *
+ * TYPE: BYTE ARRAY
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_DATA = "data";
+
+ /**
+ * The name of the Intent-extra used to identify the pixel offset from the left side of the accessory
+ * display
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_X_OFFSET = "x_offset";
+
+ /**
+ * The name of the Intent-extra used to identify the pixel offset from the top of the accessory
+ * display
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_Y_OFFSET = "y_offset";
+
+ /**
+ * The name of the Intent-extra used to identify the type of key event
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #KEY_ACTION_PRESS}
+ * - {@link #KEY_ACTION_RELEASE}
+ * - {@link #KEY_ACTION_REPEAT}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_KEY_ACTION = "event_type";
+
+ /**
+ * The name of the Intent-extra used to carry the time stamp of the key or touch event
+ *
+ * TYPE: INTEGER (long)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_TIMESTAMP = "timestamp";
+
+ /**
+ * The name of the Intent-extra used to identify the keycode.
+ * Information about what type of keypad a accessory has can be found using the
+ * Registration & Capabilities API
+ *
+ * ALLOWED VALUES:
+ * Any key code defined in the {@link KeyCodes} interface.
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_KEY_CODE = "key_code";
+
+ /**
+ * The name of the Intent-extra used to indicate the touch action
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #TOUCH_ACTION_PRESS}
+ * - {@link #TOUCH_ACTION_LONGPRESS}
+ * - {@link #TOUCH_ACTION_RELEASE}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_TOUCH_ACTION = "action";
+
+ /**
+ * The name of the Intent-extra used to indicate the direction
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #SWIPE_DIRECTION_UP}
+ * - {@link #SWIPE_DIRECTION_DOWN}
+ * - {@link #SWIPE_DIRECTION_LEFT}
+ * - {@link #SWIPE_DIRECTION_RIGHT}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SWIPE_DIRECTION = "direction";
+
+ /**
+ * The name of the Intent-extra used to carry the X coordinate of the touch event
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_X_POS = "x_pos";
+
+ /**
+ * The name of the Intent-extra used to carry the Y coordinate of the touch event
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_Y_POS = "y_pos";
+
+ /**
+ * The name of the Intent-extra used to carry the error code
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - 0: 'Registration information missing'
+ * - 1: 'Accessory not connected'
+ * - 2: 'Host Application busy'
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ERROR_CODE = "error_code";
+
+ /**
+ * The name of the Intent-extra containing the key set by the extension.
+ * This Intent-data is present in all Intents sent by accessory host application,
+ * except where {@link android.app.Activity#startActivity(android.content.Intent)}
+ * is used. See section Security
+ * for more information
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EXTENSION_KEY = "extension_key";
+
+ /**
+ * The name of the Intent-extra used to identify the data XML layout to be processed by the host application
+ * and displayed by the accessory.
+ * The layout resource id is used to identify the layout.
+ *
+ * This is a standard Android layout, where a subset of the Android views are supported.
+ *
+ * Dimensions
+ *
+ * The px dimensions is the only dimension supported.
+ * The only exception is text sizes which can be specified using sp to indicate that the text
+ * shall be scaled to the user preference setting on the accessory.
+ *
+ * ViewGroups
+ *
+ * The following ViewGroups are supported:
+ *
+ * - AbsoluteLayout
+ * - FrameLayout
+ * - LinearLayout
+ * - RelativeLayout
+ *
+ * All XML attributes are supported for the supported ViewGroups.
+ *
+ * Views
+ *
+ * The following Views are supported:
+ *
+ * - View
+ * - ImageView
+ * - TextView
+ * - ListView
+ * - Gallery
+ *
+ * An accessory may support only a subset of these layouts.
+ * {@link DeviceColumns#LAYOUT_SUPPORT} specifies which Views that are
+ * supported for a certain accessory.
+ *
+ *
+ * The following View XML attributes are supported
+ *
+ * - android:background - restricted to a solid color such as "#ff000000" (black)
+ * - android:clickable - {@link #CONTROL_OBJECT_CLICK_EVENT_INTENT} are sent for views that are clickable
+ * - android:id
+ * - android:layout_height
+ * - android:layout_width
+ * - android:padding
+ * - android:paddingBottom
+ * - android:paddingLeft
+ * - android:paddingRight
+ * - android:paddingTop
+ *
+ *
+ * ImageView
+ *
+ * For an ImageView the following XML attributes are supported
+ *
+ * - android:src - can be a BitmapDrawable or a NinePatchDrawable
+ * - android:scaleType
+ *
+ *
+ * TextView
+ *
+ * For a TextView the following XML attributes are supported
+ *
+ * - android:ellipsize - can be none, start, middle or end
+ * - android:gravity
+ * - android:lines
+ * - android:maxLines
+ * - android:singleLine
+ * - android:text
+ * - android:textColor
+ * - android:textSize - Not all text sizes are supported by all accessories.
+ * If a not supported text size is used the accessory will select the closest available text size.
+ * See the accessory white paper for a list of supported text sizes.
+ *
+ * If the sp unit is used the text is scaled according to settings on the accessories (if supported by the accessory).
+ * If the px unit is used the text is not affected by any settings on the accessory.
+ *
+ * - android:textStyle
+ *
+ *
+ * ListView and Gallery
+ *
+ * For a ListView and a Gallery there are some additional limitations.
+ * These views always have to fill the entire display width.
+ * The items in these views also have to fill the entire display width.
+ * The height of an item may not be larger than the height of the parent view.
+ *
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_DATA_XML_LAYOUT = "data_xml_layout";
+
+ /**
+ * The name of the Intent-extra used to identify a reference within a layout.
+ * Corresponds to the android:id XML attribute in the layout.
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LAYOUT_REFERENCE = "layout_reference";
+
+ /**
+ * The name of the Intent-extra used when sending a text (String)
+ * from the extension to the accessory. The accessory will map the text
+ * to a layout reference.
+ *
+ * TYPE: STRING
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_TEXT = "text_from extension";
+
+ /**
+ * The name of the Intent-extra used for indicating the status of the Active Power State Mode.
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - ACTIVE_POWER_SAVE_MODE_OFF
+ * - ACTIVE_POWER_SAVE_MODE_ON
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_ACTIVE_POWER_MODE_STATUS = "active_power_mode_status";
+
+ /**
+ * Data used to populate the views in a XML layout with dynamic info.
+ * For example updating a TextView with a new text or setting a new
+ * image in an ImageView. {@link #EXTRA_LAYOUT_REFERENCE} specifies the
+ * view to be updated and one of {@link #EXTRA_TEXT},
+ * {@link #EXTRA_DATA_URI} and {@link #EXTRA_DATA} specifies the new
+ * information in the view.
+ *
+ * TYPE: Array of BUNDLEs with following information in each BUNDLE.
+ *
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_TEXT} or {@link #EXTRA_DATA_URI} or {@link #EXTRA_DATA}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LAYOUT_DATA = "layout_data";
+
+ /**
+ * Data to populate a ListView with initial content.
+ *
+ *
+ * TYPE: Array of BUNDLEs with following information in each BUNDLE.
+ *
+ * - {@link #EXTRA_DATA_XML_LAYOUT}
+ * - {@link #EXTRA_LIST_ITEM_ID}
+ * - {@link #EXTRA_LIST_ITEM_POSITION}
+ * - {@link #EXTRA_LAYOUT_DATA} (optional)
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_CONTENT = "list_content";
+
+ /**
+ * The type of a click.
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #CLICK_TYPE_SHORT}
+ * - {@link #CLICK_TYPE_LONG}
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_CLICK_TYPE = "click_type";
+
+ /**
+ * A unique identity of a list item assigned by the extension.
+ * This can for example be the row id from a database.
+ *
+ * TYPE: INTEGER
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_ITEM_ID = "list_item_id";
+
+ /**
+ * The position in a list. The position is in the range from 0 to the
+ * {@link #EXTRA_LIST_COUNT}-1.
+ *
+ * TYPE: INTEGER
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_ITEM_POSITION = "list_item_position";
+
+ /**
+ * The number of items in a list.
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_COUNT = "list_count";
+
+ /**
+ * Reference to a view in a list item layout.
+ * Corresponds to the android:id XML attribute in the layout.
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_ITEM_LAYOUT_REFERENCE = "list_item_layout_reference";
+
+ /**
+ * If true then the user is allowed to initiate a refresh of the list
+ * content. (For example by a pull to refresh gesture.)
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LIST_REFRESH_ALLOWED = "list_referesh_allowed";
+
+ /**
+ * The id of the display.
+ * Refers to {@link Registration.DisplayColumns#_ID}.
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_DISPLAY_ID = "displayId";
+
+ /**
+ * The URI of an icon (40x40 pixels) for a menu item.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_MENU_ITEM_ICON = "menuItemIcon";
+
+ /**
+ * The text for a menu item.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_MENU_ITEM_TEXT = "menuItemText";
+
+ /**
+ * A unique identity of a menu item assigned by the extension.
+ *
+ * TYPE: INTEGER
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_MENU_ITEM_ID = "menuItemId";
+
+ /**
+ * Items for a menu. Each menu item can either be an icon or a text
+ * string. The {@link #EXTRA_MENU_ITEM_ID} is used to identify the
+ * selected menu item. {@link Registration.DisplayColumns#MENU_ITEMS}
+ * specifies the number of menu items supported by the display.
+ *
+ * TYPE: Array of BUNDLEs with following information in each BUNDLE.
+ *
+ * - {@link #EXTRA_MENU_ITEM_ID}
+ * - {@link #EXTRA_MENU_ITEM_ICON} or {@link #EXTRA_MENU_ITEM_TEXT}
+ *
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_MENU_ITEMS = "menuItems";
+
+ /**
+ * The touch action is a press event.
+ *
+ * @since 1.0
+ */
+ static final int TOUCH_ACTION_PRESS = 0;
+
+ /**
+ * The touch action is a long press event
+ *
+ * @since 1.0
+ */
+ static final int TOUCH_ACTION_LONGPRESS = 1;
+
+ /**
+ * The touch action is a release event
+ *
+ * @since 1.0
+ */
+ static final int TOUCH_ACTION_RELEASE = 2;
+
+ /**
+ * The direction of the swipe event is up
+ *
+ * @since 1.0
+ */
+ static final int SWIPE_DIRECTION_UP = 0;
+
+ /**
+ * The direction of the swipe event is down
+ *
+ * @since 1.0
+ */
+ static final int SWIPE_DIRECTION_DOWN = 1;
+
+ /**
+ * The direction of the swipe event is left
+ *
+ * @since 1.0
+ */
+ static final int SWIPE_DIRECTION_LEFT = 2;
+
+ /**
+ * The direction of the swipe event is right
+ *
+ * @since 1.0
+ */
+ static final int SWIPE_DIRECTION_RIGHT = 3;
+
+ /**
+ * The screen off state
+ *
+ * @since 1.0
+ */
+ static final int SCREEN_STATE_OFF = 0;
+
+ /**
+ * The screen dim state
+ *
+ * @since 1.0
+ */
+ static final int SCREEN_STATE_DIM = 1;
+
+ /**
+ * The screen on state
+ *
+ * @since 1.0
+ */
+ static final int SCREEN_STATE_ON = 2;
+
+ /**
+ * The screen state is automatically handled by the host application
+ *
+ * @since 1.0
+ */
+ static final int SCREEN_STATE_AUTO = 3;
+
+ /**
+ * The key event is a key press event
+ *
+ * @since 1.0
+ */
+ static final int KEY_ACTION_PRESS = 0;
+
+ /**
+ * The key event is a key release event
+ *
+ * @since 1.0
+ */
+ static final int KEY_ACTION_RELEASE = 1;
+
+ /**
+ * The key event is a key repeat event
+ *
+ * @since 1.0
+ */
+ static final int KEY_ACTION_REPEAT = 2;
+
+ /**
+ * The control action is turned on
+ *
+ * @since 1.0
+ */
+ static final int CONTROL_ACTION_ON = 0;
+
+ /**
+ * The control action is turned off
+ *
+ * @since 1.0
+ */
+ static final int CONTROL_ACTION_OFF = 1;
+
+ /**
+ * Vibration or LED is repeated until explicitly stopped
+ *
+ * @since 1.0
+ */
+ static final int REPEAT_UNTIL_STOP_INTENT = -1;
+
+ /**
+ * Constant defining active power safe mode OFF.
+ *
+ * @since 2.0
+ */
+ static final int ACTIVE_POWER_SAVE_MODE_OFF = 0;
+
+ /**
+ * Constant defining active power safe mode ON.
+ *
+ * @since 2.0
+ */
+ static final int ACTIVE_POWER_SAVE_MODE_ON = 1;
+
+ /**
+ * A click is a short click.
+ *
+ * @since 2.0
+ */
+ static final int CLICK_TYPE_SHORT = 0;
+
+ /**
+ * The click is a long click.
+ *
+ * @since 2.0
+ */
+ static final int CLICK_TYPE_LONG = 1;
+ }
+
+ /**
+ * Interface used to define constants for
+ * keycodes
+ */
+ public interface KeyCodes {
+
+ /**
+ * Keycode representing a play button
+ */
+ static final int KEYCODE_PLAY = 1;
+
+ /**
+ * Keycode representing a next button
+ */
+ static final int KEYCODE_NEXT = 2;
+
+ /**
+ * Keycode representing a previous button
+ */
+ static final int KEYCODE_PREVIOUS = 3;
+
+ /**
+ * Keycode representing an action button
+ */
+ static final int KEYCODE_ACTION = 4;
+
+ /**
+ * Keycode representing a volume down button
+ */
+ static final int KEYCODE_VOLUME_DOWN = 5;
+
+ /**
+ * Keycode representing a volume up button
+ */
+ static final int KEYCODE_VOLUME_UP = 6;
+
+ /**
+ * Keycode representing a back button
+ */
+ static final int KEYCODE_BACK = 7;
+
+ /**
+ * Keycode representing an options button
+ */
+ static final int KEYCODE_OPTIONS = 8;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/aef/control/package-info.java b/src/com/sonyericsson/extras/liveware/aef/control/package-info.java
new file mode 100644
index 0000000..1e3f548
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/control/package-info.java
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * The Control API, part of the Smart Extension APIs for Sony's smart
+ * accessories, enables an app extension to take total control of the accessory.
+ * It takes control over the display, LEDs, vibrator and input events.
+ *
+ * Refer to {@link com.sonyericsson.extras.liveware.aef.control.Control} for a detailed description
+ * of the Control API.
+ */
+package com.sonyericsson.extras.liveware.aef.control;
+
diff --git a/src/com/sonyericsson/extras/liveware/aef/notification/Notification.java b/src/com/sonyericsson/extras/liveware/aef/notification/Notification.java
new file mode 100644
index 0000000..740db78
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/notification/Notification.java
@@ -0,0 +1,1121 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (C) 2012-2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.aef.notification;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.ExtensionColumns;
+
+/**
+ * Overview
+ * Notification is a part of the Smart Extension APIs.
+ * The Notification engine enables the gathering of event-type data from different
+ * sources to one place so that accessory host applications will be able to
+ * access this data, instead of getting the data from each individual source.
+ * Examples of event-data are activity streams on a social network, new incoming
+ * SMS and MMS message notifications, a missed call notification, etc.
+ *
+ *
+ * Application developers who wish to have their event-data presented by
+ * accessories (granted with the permission to access the Notification
+ * engine's data) should input their application's data according to the
+ * schema defined by the Notification API.
+ *
+ *
+ * The following diagram shows the position of the Notification API in its
+ * operating context.
+ *
+ *
+ *
+ *
+ * The Notification API defines and implements an Android ContentProvider that
+ * extensions access via the Android ContentResolver API. The ContentProvider
+ * implementation is backed by a database implementation. In order for an extension
+ * to interact with Notification API, the extension must have used the Registration API
+ * and inserted information in the extension table. See Registration & Capabilities API
+ * for more information on how to insert a record in the extension table.
+ * When needed, Android Intents are sent to the extensions to perform a task.
+ *
+ *
+ * Extensions contribute their data using the set format dictated by the Notification API.
+ * As they are standalone Android applications in their own right,
+ * extensions may be uninstalled any time, unless they are part of the system
+ * image, and may be installed any time during the operation of the device. They
+ * may be 'disabled' as well by the end user via the user interface or by the
+ * extension developer; when 'disabled', data from that extension may not be
+ * displayed by the accessory host applications.
+ *
+ *
+ * The accessory host applications provide the functionality to control and
+ * present the data that is collected by the notification engine. Depending on the
+ * purpose of the application, the notification engine may not be the only data
+ * source the application interacts with. Accessory host applications have
+ * read-access to the data provided by all the extensions, including the right to
+ * update some of the data fields. Due to this reason, access needs to be controlled
+ * and restricted so that unwanted information leaks are prevented;
+ * this is done through the use of a permission.
+ *
+ *
+ * The purpose of the engine is to provide a central store for event-data from
+ * different sources that is of interest to present to the end user. The reasons
+ * for choosing such a design are accessory host application performance and data
+ * security. Cross-database queries are slow and even slower when there are
+ * potentially many databases involved and this will severely impact the
+ * performance of accessories and their perceived user experience.
+ * It is difficult and practically impossible to allow the 'correct'
+ * applications to access the extension-data when there are many databases to
+ * interact with. However tempting it may be, the purpose of the engine is NOT to
+ * be a central store for all kinds of data, e.g. files, media etc., such that it
+ * will be a "store room" for all kinds of extension-data.
+ *
+ * Topics covered here:
+ *
+ * - Concept Explanations
+ *
- Inter-application communication
+ *
- Security
+ *
- Extension Lifecycle
+ *
- Adding a Source
+ *
- Getting event-data
+ *
- Showing the Detail View of an Event
+ *
- Contact Linking
+ *
- Handling Images
+ *
- Data Integrity
+ *
- Performance
+ *
+ *
+ *
+ * Concept Explanations
+ *
+ * There are three fundamental concepts in the Notification engine's database
+ * extension developers are required to understand.
+ *
+ *
+ * The concept of Extension is on Android APK level. The extension table
+ * of the registration database contains meta-information about each extension.
+ * The purpose of the extension is to provide the necessary data to the notification
+ * engine set by the database schema. The source of the extension's event-data may
+ * be self-generated, other Android ContentProvider, a Web server or a combination of
+ * these. The extension is a standalone application which may have its own GUI that
+ * also has the capability to provide data to be shown by a host application
+ * using the Notification engine, or it may not have its own GUI and it is
+ * completely dependent on the host applications that use the Notification engine to
+ * present its data.
+ *
+ *
+ * Source is a logical abstraction introduced to enable extension developers
+ * who want to distinguish the presentation of data connected to different
+ * backends but retain the ability to package these in a standalone APK. A use
+ * case example is an email aggregator extension that allows the user to connect to
+ * different email accounts through the installation of only one Android package
+ * file; each email account can be set as a Source or the extension defines only
+ * one Source. In the latter scenario, emails from all accounts may be shown
+ * in one view instead of separate views. {@link Source} stores attribute
+ * information about a Source. The accessory host application may use this
+ * information to filter event-data by Source or provide
+ * configuration options on the user interface to filter event-data by
+ * Source.Extension developers who wish to have the accessory host
+ * application display events from different Sources
+ * clearly should add {@link Source} information.
+ * Up to 8 sources can be linked to a Extension.
+ * If the limit
+ * is reached, an exception will be thrown. A Source always has to be
+ * linked to an Extension .
+ *
+ *
+ * An Event is a representation of a notification that may be noteworthy
+ * to present to the end user. Examples of events are incoming SMS
+ * message notifications, a missed call notification, updates from friends on a
+ * social network etc. {@link Event} is used to store events provided
+ * by the extensions. The accessory host application typically uses the information
+ * in this table to present the data. An Event is always connected
+ * to a Source but a Source may not always have to have an
+ * Event.
+ * Maximum 100 events from a Source
+ * stored in {@link Event}; when the limit is reached, events will be automatically
+ * removed.
+ *
+ *
+ *
+ * Inter-application communication
+ *
+ * Extensions only use the Android ContentResolver API to communicate with the
+ * Notification engine's ContentProvider. For the possibility to react to user
+ * input, extensions should implement at least an Android BroadcastReceiver to
+ * catch Intents sent from the accessory host
+ * applications. Also see the Control API, Widget API and Sensor API documentation.
+ *
+ *
+ * The list and descriptions of each BroadcastIntent extensions could listen to
+ * are found in {@link Intents} together with the Intent-extra data
+ * that are sent in each Intent.
+ *
+ *
+ * Security
+ *
+ * In order to use the Notification API, an extension must first add information
+ * in the extension table. This require a specific permission. See the documentation
+ * of the Registration API for more information
+ *
+ *
+ * A extension only has access to its own data: it is able to insert, query, update
+ * and remove its data that is stored on the Notification engine. When an
+ * application registered as a extension is uninstalled from the Android system,
+ * the associated data that is stored in the engine is automatically removed
+ * by the Notification engine's implementation.
+ *
+ *
+ * If a extension developer wishes to allow another application to access its data
+ * on the engine through the use of sharedUserId, it is possible to do so
+ * though not recommended. When these two or more applications are registered as
+ * extensions, the Notification engine's security mechanism will only treat these
+ * extensions as one extension and the extension developer is responsible for any leakage
+ * or misuse of its information stored in the Notification engine's content
+ * provider.
+ *
+ *
+ * Extension developers are free to sign their applications with their own
+ * certificate.
+ *
+ *
+ * Extension Lifecycle
+ *
+ * Before an application can take full advantage of the Notification engine,
+ * it must tell the engine that it exists and for the engine to have a record
+ * of this application's attributes. This process is known as registration and
+ * after a successful registration, the application is referred as an extension in
+ * the Notification engine's context. In practice, the process of registering
+ * a extension involves the application inserting some data about itself using the
+ * Registration API.
+ *
+ *
+ * From the Notification engine's perspective, the life cycle of a extension starts
+ * from the time a successful registration takes place to the time the Android
+ * system uninstalls the application or the extension deregisters itself from the
+ * Notification engine. During this time, the extension is free to access the Event
+ * Stream engine and receive Intents from it.
+ *
+ *
+ * Adding a Source
+ *
+ * As explained in an earlier section, Source is a logical abstraction
+ * introduced to easily enable the presentation of event-data originating from
+ * different backend. It is up to the extension developer to decide how to segment
+ * the event-data contributed by that extension, but all event-data must be connected
+ * to a Source, or else the insert operation for event-data will fail.
+ *
+ *
+ * Setting the Source information in the ContentProvider should take place after
+ * the extension is successfully registered and before inserting event-data.
+ *
+ * ContentValues values = new ContentValues();
+ * Builder iconUriBuilder = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ * .authority(getPackageName())
+ * .appendPath(Integer.toString(R.drawable.icon));
+ *
+ * values.put(SourceColumns.NAME, "RSS news feed");
+ * values.put(SourceColumns.ENABLED, "1");
+ * values.put(SourceColumns.ICON_URI_1, iconUriBuilder.toString());
+ *
+ * ...
+ *
+ * uri = cr.insert(Source.URI, values);
+ *
+ *
+ *
+ * Getting event-data
+ * Your extension may automatically retrieve event-data periodically from the source where the
+ * event-data is generated or rely on an Intent sent by the host application to trigger
+ * the retrieval.
+ *
+ *
+ * The name of the Intent is {@link Intents#REFRESH_REQUEST_INTENT}.
+ * Define your BroadcastReceiver to receive this Intent if you wish to rely on this to
+ * trigger the event-data retrieval. Do not rely on the interval when this Intent is sent
+ * as it may be arbitrary and completely dependent on the implementation of the Intent sender.
+ * However it is always sent when a host application is started and an extension can use this to start
+ * collecting data and continue to be be active.
+ *
+ *
+ * When your extension has event-data to insert to the Notification engine's
+ * ContentProvider, it may use
+ * {@link android.content.ContentResolver#insert(Uri, android.content.ContentValues)}
+ * or {@link android.content.ContentResolver#bulkInsert(Uri, android.content.ContentValues[])}.
+ * The latter method is recommended for performance reasons to use when there are many
+ * rows of event-data to insert.
+ *
+ * ContentResolver cr = getContentResolver();
+ * ContentValues[] valueArray = new ContentValues[count];
+ * < fill valueArray with data >
+ * cr.bulkInsert(Event.URI, valueArray);
+ *
+ *
+ *
+ * When retrieving event-data from the data source, it is highly recommended your
+ * extension updates and retrieves other relevant data from the same data source,
+ * e.g. the user's latest status update, at around the same time. This is to
+ * minimize network signaling traffic and latency. It is also costly for battery
+ * consumption if there is too frequent network signaling activity.
+ *
+ *
+ * If you need to synchronize periodically with a server in a network, consider
+ * using the {@link android.app.AlarmManager} to achieve optimal power consumption.
+ * A tutorial explaining how you can implement this is posted at the
+ *
+ * Sony Ericsson Developer blog.
+ *
+ *
+ * Showing the Detail View of an Event
+ *
+ * The event-data supplied by your plug-in in {@link Event} may be a snapshot of
+ * the information and the user has limited possibilities to interact with the
+ * information presented by the accessory host application. The user may wish to
+ * see all details related to that event and react to it, e.g. mark it as a
+ * favorite, reply, watch the video etc. If your extension offers the user the
+ * opportunity to interact with the event in your application or on a website,
+ * listen for the {@link Intents#VIEW_EVENT_INTENT} Intent. This Intent
+ * is sent by the accessory host application when the user performs an action
+ * signaling the intention to view the event details. The Intent contains Intent-
+ * data about the specific event-data that will enable your extension to launch the
+ * detail view of that event.
+ *
+ *
+ *
+ *
Handling Images
+ *
+ * The location of images is represented as a string. Images may be stored
+ * locally on the device or on the SD card.
+ *
+ *
+ * The following URI schemes are supported:
+ *
+ *
+ * - android.resource://
+ *
+ * -
+ * content://
+ *
+ *
+ *
+ *
+ *
Data Integrity
+ *
+ * In order to have a consistent database, the Notification Engine will enforce
+ * database data integrity upon any data inserted or updated by extensions. This is
+ * especially true for foreign keys. As an example, sourceId for an
+ * Event is a foreign key to the column _id in the Source
+ * table, thus the source_id must have a valid reference to a row in the
+ * Source table which in turn is associated with a plug-in. If values for
+ * the stated mandatory columns are not provided, SQLExceptions with constraint
+ * failures will be thrown.
+ *
+ *
+ * Performance
+ *
+ * For best performance, it is recommended the extension developer use
+ * {@link android.content.ContentResolver#bulkInsert(Uri, android.content.ContentValues[])}
+ * or {@link android.content.ContentResolver#applyBatch(String, java.util.ArrayList)}
+ * when doing inserts or updates to the Event Stream's ContentProvider.
+ *
+ */
+public class Notification {
+
+ /**
+ * @hide
+ * This class is only intended as a utility class containing declared constants
+ * that will be used by notification extension developers.
+ */
+ protected Notification(){
+ }
+
+ /**
+ * Authority for the Notification provider.
+ *
+ * @since 1.0
+ */
+ public static final String AUTHORITY = "com.sonyericsson.extras.liveware.aef.notification";
+
+ /**
+ * Base URI for the Notification provider.
+ *
+ * @since 1.0
+ */
+ protected static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * Broadcast Intents sent to extensions by the host application.
+ */
+ public interface Intents {
+
+ /**
+ * Intent sent by the host application to the relevant extension
+ * to display all details related to the event
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_EVENT_ID}
+ * - {@link #EXTRA_SOURCE_ID}
+ * - {@link #EXTRA_ACTION}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ *
+ * @since 1.0
+ */
+ static final String VIEW_EVENT_INTENT = "com.sonyericsson.extras.liveware.aef.notification.VIEW_EVENT_DETAIL";
+
+ /**
+ * Intent sent by the host application when an update of available data is needed
+ *
+ * Intent-extra data:
+ *
+ *
+ - {@link #EXTRA_EXTENSION_KEY}
+ *
+ * @since 1.0
+ */
+ static final String REFRESH_REQUEST_INTENT = "com.sonyericsson.extras.liveware.aef.notification.REFRESH_REQUEST";
+
+ /**
+ * The name of the Intent-extra used to identify the event
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EVENT_ID = "event_id";
+
+ /**
+ * The name of the Intent-extra used to identify which Source an
+ * Event is associated with
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SOURCE_ID = "source_id";
+
+ /**
+ * The action requested by the user.
+ * This is a string indicating a user action
+ * corresponding to one of the three actions that are
+ * defined in the source table
+ * action_1 {@link SourceColumns#ACTION_1}
+ * action_2 {@link SourceColumns#ACTION_2}
+ * action_3 {@link SourceColumns#ACTION_3}
+ * action_icon_1 {@link SourceColumns#ACTION_ICON_1}
+ * action_icon_2 {@link SourceColumns#ACTION_ICON_2}
+ * action_icon_3 {@link SourceColumns#ACTION_ICON_3}
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #EXTENSION_ACTION_1}
+ * - {@link #EXTENSION_ACTION_2}
+ * - {@link #EXTENSION_ACTION_3}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ACTION = "action";
+
+ /**
+ * The name of the Intent-extra containing the key set by the extension
+ * in {@link ExtensionColumns#EXTENSION_KEY}. This Intent-data is present in
+ * all Intents sent by accessory host application, except where
+ * {@link android.app.Activity#startActivity(android.content.Intent)}
+ * is used. See section Security
+ * for more information
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EXTENSION_KEY = "extension_key";
+
+ /**
+ * The name of the Intent-extra used to identify the Host Application.
+ * The Host Application will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AHA_PACKAGE_NAME = "aha_package_name";
+
+ /**
+ * Constant defining an action requested by the host application
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * The action corresponds the action that is
+ * defined in the source table action_1 {@link SourceColumns#ACTION_1}
+ * or action_icon_1 {@link SourceColumns#ACTION_ICON_1}
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ACTION_1 = "action_1";
+
+ /**
+ * Constant defining an action requested by the host application
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * The action corresponds the action that is
+ * defined in the source table action_2 {@link SourceColumns#ACTION_2}
+ * or action_icon_2 {@link SourceColumns#ACTION_ICON_2}
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ACTION_2 = "action_2";
+
+ /**
+ * Constant defining an action requested by the host application
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * The action corresponds the action that is
+ * defined in the source table action_3 {@link SourceColumns#ACTION_3}
+ * or action_icon_3 {@link SourceColumns#ACTION_ICON_3}
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ACTION_3 = "action_3";
+ }
+
+ /**
+ * Definitions used for interacting with the Extension-table.
+ *
+ */
+ public interface Source {
+
+ /**
+ * The source table name
+ */
+ static final String TABLE_NAME = "source";
+
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-source";
+
+ /**
+ * Path segment
+ */
+ static final String SOURCES_PATH = "source";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, SOURCES_PATH);
+ }
+
+ /**
+ * Column-definitions for the Source table.
+ */
+ public interface SourceColumns extends BaseColumns {
+
+ /**
+ * Displayable name of the source
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String NAME = "name";
+
+ /**
+ * Each Source can use up to 2 icons with different
+ * sizes and one monochrome
+ * This is the URI of the largest icon (30x30 pixels)
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ICON_URI_1 = "iconUri1";
+
+ /**
+ * Each Source can use up to 2 icons with different
+ * sizes and one monochrome
+ * This is the URI of the second largest icon (18x18 pixels)
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ICON_URI_2 = "iconUri2";
+
+ /**
+ * Each Source can use up to 2 icons with different
+ * sizes and one monochrome
+ * This is the URI of the monochrome icon (18x18 pixels)
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ICON_URI_BLACK_WHITE = "iconUriBlackWhite";
+
+ /**
+ * Indicates if the source is enabled
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String ENABLED = "enabled";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the text for the action.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ACTION_1 = "action_1";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the text for the action.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ACTION_2 = "action_2";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the text for the action.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String ACTION_3 = "action_3";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the URI for the action icon (40x40 pixels).
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 2.0
+ */
+ static final String ACTION_ICON_1 = "action_icon_1";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the URI for the action icon (40x40 pixels).
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 2.0
+ */
+ static final String ACTION_ICON_2 = "action_icon_2";
+
+ /**
+ * Action supported by the extension.
+ * The action is defined by the extension and supported
+ * for this source.
+ * Actions are sent to the extension from host applications
+ * using the {@link Intents#VIEW_EVENT_INTENT} intent.
+ * This is the URI for the action icon (40x40 pixels).
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 2.0
+ */
+ static final String ACTION_ICON_3 = "action_icon_3";
+
+ /**
+ * The time (in milliseconds since January 1, 1970 00:00:00 UTC UNIX
+ * EPOCH) when an event linked to this source was created.
+ * Shall be stored as GMT+0 time
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String UPDATE_TIME = "updateTime";
+
+ /**
+ * Text to speech specific text.
+ * The text in this column is used in combination
+ * with the events of the source to create speech events.
+ * The text in this column is read out before the events
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String TEXT_TO_SPEECH = "textToSpeech";
+
+ /**
+ * Extension specific identifier of the source
+ * It is up to the extension to define this identifier
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_SPECIFIC_ID = "extension_specific_id";
+
+ /**
+ * The color associated with this source.
+ * This color will be used when visualizing the source and the events associated with it.
+ * The color shall be sent in the format specified in {@link android.graphics.Color}.
+ * There is no support for transparency (alpha is ignored). If not set the color is
+ * determined by the host application.
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ * @since 2.0
+ */
+ static final String COLOR = "color";
+
+ /**
+ * The package name of a plug-in.
+ * If an extension supports shared user id, the package name
+ * must be specified
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL (REQUIRED if shared user id is used by extension)
+ *
+ * @since 1.0
+ */
+ static final String PACKAGE_NAME = "packageName";
+
+ /**
+ * If true it is possible for the user to trigger a manual refresh of events from this
+ * source.
+ * The manual refresh can for example be used to trigger a new poll to a server.
+ * The {@link Intents#REFRESH_REQUEST_INTENT} is sent to the extension when the user has
+ * initiated a refresh.
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL (Default behavior is FALSE)
+ *
+ * @since 2.0
+ */
+ static final String SUPPORTS_REFRESH = "supportsRefresh";
+ }
+
+ /**
+ * Definitions used for interacting with the event table.
+ *
+ */
+ public interface Event {
+
+ /**
+ * The event table name
+ */
+ static final String TABLE_NAME = "event";
+
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-event";
+
+ /**
+ * Path segment
+ */
+ static final String EVENTS_PATH = "event";
+
+ /**
+ * Path segment
+ */
+ static final String EVENT_READ_STATUS_PATH = "read_status";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, EVENTS_PATH);
+
+ /**
+ * Content URI used to observe changes in EVENT_READ_STATUS
+ */
+ static final Uri READ_STATUS_URI = Uri.withAppendedPath(BASE_URI, EVENT_READ_STATUS_PATH);
+ }
+
+ /**
+ * Column-definitions for the event table.
+ */
+ public interface EventColumns extends BaseColumns {
+
+ /**
+ * The ID of the host source corresponding to
+ * this event
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: MANDATORY
+ *
+ *
+ * @since 1.0
+ */
+ static final String SOURCE_ID = "sourceId";
+
+ /**
+ * Short text describing the title for event linked with this data row.
+ * This can be the phone number, username, email address etc
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String TITLE = "title";
+
+ /**
+ * Content URI to an image linked with the event at this data row
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String IMAGE_URI = "imageUri";
+
+ /**
+ * The time (in milliseconds since January 1, 1970 00:00:00 UTC UNIX
+ * EPOCH) when the content linked with this data row was published on
+ * the source. Shall be stored as GMT+0 time
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String PUBLISHED_TIME = "publishedTime";
+
+ /**
+ * Whether the event linked with this data row is specifically directed
+ * to the user ("me") or concerns the user ("me"), e.g. received SMS,
+ * Facebook private message to the logged-in user, Facebook private
+ * message from the logged-in user, @reply Tweets from the logged-in
+ * user, user ("me") is tagged in a photo etc
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - 0: 'not personal'
+ * - 1: 'personal'
+ *
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String PERSONAL = "personal";
+
+ /**
+ * Message associated with this event
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String MESSAGE = "message";
+
+ /**
+ * Geo data associated with this event
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String GEO_DATA = "geoData";
+
+ /**
+ * Indicates if the event has been read by the user
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String EVENT_READ_STATUS = "readStatus";
+
+ /**
+ * The time (in milliseconds since January 1, 1970 00:00:00 UTC UNIX
+ * EPOCH) when this row was created.
+ * The time stamp is set automatically
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: The time stamp is set automatically
+ *
+ *
+ * @since 1.0
+ */
+ static final String TIME_STAMP = "timeStamp";
+
+ /**
+ * Displayable name of the user linked with this data row, e.g. full
+ * name
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1
+ */
+ static final String DISPLAY_NAME = "display_name";
+
+ /**
+ * URI to the profile image of the user linked with this data row
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1
+ */
+ static final String PROFILE_IMAGE_URI = "profile_image_uri";
+
+ /**
+ * A reference to the contacts content provider.
+ * The reference is a URI to a {@link android.provider.ContactsContract.RawContacts}
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1
+ */
+ static final String CONTACTS_REFERENCE = "contacts_reference";
+
+ /**
+ * Generic data column for use by the plug-in to store information that
+ * may be used to identify the friend that is at this data row, in its
+ * domain. See section
+ * Contact Linking for more information
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1
+ */
+ static final String FRIEND_KEY = "friend_key";
+ }
+
+ /**
+ * Definitions used for interacting with the source event join query.
+ *
+ */
+ public interface SourceEvent {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-source-event";
+
+ /**
+ * Path segment
+ */
+ static final String SOURCES_EVENTS_PATH = "source_event";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, SOURCES_EVENTS_PATH);
+ }
+
+
+ /**
+ * Column-definitions for the source event join query.
+ */
+ public interface SourceEventColumns extends SourceColumns, EventColumns {
+ /**
+ * The ID of the event in the source event join query.
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ */
+ static final String EVENT_ID = "eventId";
+ }
+ }
diff --git a/src/com/sonyericsson/extras/liveware/aef/notification/package-info.java b/src/com/sonyericsson/extras/liveware/aef/notification/package-info.java
new file mode 100644
index 0000000..43cb07c
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/notification/package-info.java
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * The Notification API, part of the Smart Extension APIs for Sony's
+ * smart accessories, enables gathering of event-type data from different
+ * sources into one place. This way the accessory host applications will be
+ * able to access this data from one source, instead of getting the data from
+ * each individual source.
+ *
+ * Refer to {@link com.sonyericsson.extras.liveware.aef.notification.Notification} for a detailed description
+ * of the Notification API.
+ */
+package com.sonyericsson.extras.liveware.aef.notification;
+
diff --git a/src/com/sonyericsson/extras/liveware/aef/registration/Registration.java b/src/com/sonyericsson/extras/liveware/aef/registration/Registration.java
new file mode 100644
index 0000000..72f0801
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/registration/Registration.java
@@ -0,0 +1,1728 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (C) 2012-2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.aef.registration;
+
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+import com.sonyericsson.extras.liveware.aef.widget.Widget;
+
+/**
+ * The Registration and Capability API is a part of the Smart Extension APIs
+ *
+ * This API is used by accessory extensions and accessory host applications. Typically host applications insert
+ * and maintain information about the accessories capabilities. Extensions use the capability information
+ * in order to interact with the accessories in a correct way. Before an extension can interact with an accessory it must
+ * provide (register) some information needed by the host applications.
+ * The API defines and implements an Android ContentProvider that applications access via the Android ContentResolver API.
+ * The ContentProvider implementation is backed by a database implementation.
+ *
+ * Topics covered here:
+ *
+ * - Using the capabilities API
+ *
- Extension registration
+ *
- User extension configuration
+ *
- Security.
+ *
+ *
+ * Using the capabilities API
+ *
+ * This API is an Android content provider that provides information about the capabilities of the accessories. The information is
+ * provided by the host applications and are used by the extensions to obtain necessary information in order to interact with the
+ * accessories through the Control, Sensor and Widget APIs. The content provider contains the tables shown in the picture below.
+ *
+ *
+ *
+ * For each accessory there is a corresponding record in the host_application table. A Particular host application is identified
+ * by its package name. For each host application there is one or more device records in the device table.
+ * A particular device can support zero or more displays, sensors, leds and inputs, defined in the display, sensor, led and input
+ * tables respectively. There is a sensor_type table describing each type of sensor and keypad table describing the capabilities if
+ * the keypads of each input type. The capabilities tables are accessible through the content provider.
+ *
+ * Capability URI's and description
+ *
+ * - Host application URI {@link HostApp#URI} and columns {@link HostAppColumns}
+ *
- Host application URI {@link Device#URI} and columns {@link DeviceColumns}
+ *
- Host application URI {@link Display#URI} and columns {@link DisplayColumns}
+ *
- Host application URI {@link Sensor#URI} and columns {@link SensorColumns}
+ *
- Host application URI {@link Input#URI} and columns {@link InputColumns}
+ *
- Host application URI {@link Led#URI} and columns {@link LedColumns}
+ *
- Host application URI {@link SensorType#URI} and columns {@link SensorTypeColumns}
+ *
- Host application URI {@link KeyPad#URI} and columns {@link KeyPadColumns}
+ *
+ * It is also possible to use a view that returns all capabilities in a single query.
+ * The URI is {@link Capabilities#URI}.
+ *
+ *
+ * Extension registration
+ *
+ * Before an extension can use an accessory, the extension must use the registration API content provider
+ * to insert a record in the extension table. The URI is defined in the Extension interface {@link Extension#URI} and
+ * the table scheme is defined in the ExtensionColumns interface {@link ExtensionColumns}.
+ *
+ *
+ *
+ * After inserting a record in the extensions table, the extension is ready to use the Notification API.
+ * No further registration is needed in order to use the Notification API and start writing sources and events.
+ * More advanced extensions that also want to use any of the Widget API, Control API or Sensor API must also register
+ * information in the registration table. This should be done for each host application that the extension wants to interact with.
+ * In order to find out what host applications are available and what capabilities they support, the extension should use the
+ * capability API.
+ * The URI of the registration table is defined in the ApiRegistration interface {@link ApiRegistration#URI} and
+ * the table schema is defined in the ApiRegistrationColumns interface {@link ApiRegistrationColumns}. The extension should provide
+ * the host application package name and indicate what APIs it will use.
+ *
+ *
+ * Before an application can register itself as an extension, there must be at least one host application installed on the phone.
+ * This is to prevent that extensions start writing data into the databases when there are no host applications (user has no accessories).
+ *
+ *
+ * The application should register upon reception of the
+ * {@link Intents#EXTENSION_REGISTER_REQUEST_INTENT} intent.
+ * This intent is broadcasted when a new application is installed and when a new host application
+ * has added its capabilities to the tables.
+ *
+ *
+ * User extension configuration
+ *
+ * Smart Extension apps may require configuration by the user before they can fully function.
+ * For example, the end user might need to login to a service on the Internet before events can be retrieved.
+ * The Smart Extension app is responsible for providing this configuration UI to be displayed.
+ *
+ *
+ * Host applications have their own configuration UIs from within the configuration UIs of registered
+ * Smart Extension apps can be reached.
+ * When registering to the Smart Connect, there is a column in the extension table called
+ * configurationActivity.
+ * If your Smart Extension app needs to be configured after it is registered, insert your Android
+ * Activity class name in this column.
+ *
+ *
+ * ...
+ * String configName = new ComponentName(getPackageName(),
+ * RssPluginConfig.class.getName()).flattenToShortString();
+ * values.put(ExtensionColumns.CONFIGURATION_ACTIVITY, configName);
+ * ...
+ * cr.insert(Extension.URI, values);
+ *
+ *
+ * When the user wishes to launch your configuration UI, the host application will launch the registered Activity.
+ *
+ *
+ * Security
+ *
+ * Each extension that wants to interact with the Registration & Capabilities API should
+ * specify a specific plug-in permission in their manifest file {@link Registration#EXTENSION_PERMISSION}.
+ * The API implements a security mechanism that ensure that each extension only can access their own
+ * registration and notification data. Sharing information between extensions can be obtained for extensions
+ * that use the sharedUserId mechanism, however this approach is not recommended.
+ * Extensions do not have permission to write data in the capability tables, only host applications
+ * have write access.
+ *
+ *
+ * Android Intents are sent when interaction with the extension is needed.
+ * See the documentation of the Control API, Widget API, Sensor API and
+ * Notification API for more information about intents.
+ * To enable the extension to verify the sender of the
+ * Intents is a trusted application with access to the APIs and not a malicious
+ * application that sends the same Intents on pretext of being a trusted application,
+ * the {@link ExtensionColumns#EXTENSION_KEY} field allows plug-in developers to store
+ * something that can be used as identification when the Intents are received.
+ * Except where {@link android.app.Activity#startActivity(android.content.Intent)} is used,
+ * this key is attached to every Intent the different accessory host applications send to
+ * the extension as these applications are granted access to the accessory information.
+ * The receiving extension should check the value of the key to see if it matches
+ * what it has. If not, the plug-in should ignore the Intent. This key is
+ * generated by the extension itself. It should be as unique as possible to
+ * minimize the risk of several extensions having the key. An extension is free
+ * to change the key stored in the database that it owns. As an added precaution,
+ * Intents are sent as directed Intents where possible.
+ *
+ */
+
+public class Registration {
+
+ /**
+ * @hide
+ * This class is only intended as a utility class containing declared constants
+ * that will be used by plug-in developers.
+ */
+ protected Registration(){
+ }
+
+ /**
+ * All extensions should add in their AndroidManifest.xml a
+ * tag to use this permission. The purpose is to indicate to the end user
+ * the application containing the extension interacts with the registration API.
+ *
+ * @since 1.0
+ */
+ public static final String EXTENSION_PERMISSION = "com.sonyericsson.extras.liveware.aef.EXTENSION_PERMISSION";
+
+ /**
+ * Permission used by host applications;
+ * Extensions shall use this permission to enforce security when sending intents to
+ * a host application using sendBroadcast(Intent, String)
+ *
+ * @since 1.0
+ */
+ public static final String HOSTAPP_PERMISSION = "com.sonyericsson.extras.liveware.aef.HOSTAPP_PERMISSION";
+
+ /**
+ * Authority for the Registration provider
+ *
+ * @since 1.0
+ */
+ public static final String AUTHORITY = "com.sonyericsson.extras.liveware.aef.registration";
+
+ /**
+ * Base URI for the Registration provider
+ *
+ * @since 1.0
+ */
+ protected static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * Broadcast Intents sent to extensions by the host applications
+ */
+ public interface Intents {
+
+ /**
+ * Intent sent to extensions to request
+ * extension registrations.
+ * Extensions that are already registered do not need to register again
+ * @since 1.0
+ */
+ static final String EXTENSION_REGISTER_REQUEST_INTENT = "com.sonyericsson.extras.liveware.aef.registration.EXTENSION_REGISTER_REQUEST";
+
+ /**
+ * Intent sent from the host applications to extensions to
+ * indicate that an accessory has been connected or disconnected
+ * The host application must register the status in the {@link DeviceColumns#ACCESSORY_CONNECTED}
+ * column of the device table before sending this intent
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_CONNECTION_STATUS}
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String ACCESSORY_CONNECTION_INTENT = "com.sonyericsson.extras.liveware.aef.registration.ACCESSORY_CONNECTION";
+
+ /**
+ * The name of the Intent-extra used to identify the Host Application.
+ * The Host Application will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AHA_PACKAGE_NAME = "aha_package_name";
+
+ /**
+ * The name of the Intent-extra used to identify the
+ * accessory connection status
+ *
+ * The value must one the predefined constants
+ * {@link AccessoryConnectionStatus}.
+ *
+ * TYPE: INT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_CONNECTION_STATUS = "connnection_status";
+
+ /**
+ * This Intent-extra is used when the settings
+ * of an extension is to be displayed by means
+ * of starting the activity defined in the
+ * {@link ExtensionColumns#CONFIGURATION_ACTIVITY} of the extension.
+ * The Intent-extra is only valid for extensions that support the
+ * notification API.
+ *
+ * This extra indicates if the accessory supports showing notifications history.
+ *
+ * The value can be true (default) or false.
+ * If false, the accessory does not support showing the history of notifications
+ * and a notification extension might want to hide e.g. "Clear history" from its settings
+ *
+ * TYPE: BOOLEAN
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ACCESSORY_SUPPORTS_HISTORY = "supports_history";
+
+ /**
+ * This Intent-extra is used when the settings
+ * of an extension is to be displayed by means
+ * of starting the activity defined in the
+ * {@link ExtensionColumns#CONFIGURATION_ACTIVITY} of the extension.
+ * The Intent-extra is only valid for extensions that support the
+ * notification API
+ *
+ * This extra indicates if the accessory supports triggering actions
+ * linked to notification events. For more information about actions see.
+ * {@link com.sonyericsson.extras.liveware.aef.notification.Notification.SourceColumns#ACTION_1}
+ *
+ * The value can be true (default) or false.
+ * If false, the accessory does not support triggering actions from notification events
+ * and a notification extension might want to hide action related settings
+ *
+ * TYPE: BOOLEAN
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ACCESSORY_SUPPORTS_ACTIONS = "supports_actions";
+ }
+
+ /**
+ * Interface used to define constants for
+ * accessory connection status
+ */
+ public interface AccessoryConnectionStatus {
+ /**
+ * The accessory is disconnected from the
+ * host application
+ */
+ static final int STATUS_DISCONNECTED = 0;
+
+ /**
+ * The accessory is connected to the
+ * host application
+ */
+ static final int STATUS_CONNECTED = 1;
+ }
+
+ /**
+ * Definitions used for interacting with the Extension-table
+ *
+ */
+ public interface Extension {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-extensions";
+
+ /**
+ * Path segment
+ */
+ static final String EXTENSIONS_PATH = "extensions";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, EXTENSIONS_PATH);
+ }
+
+ /**
+ * Column-definitions for the Extension table
+ */
+ public interface ExtensionColumns extends BaseColumns {
+
+ /**
+ * Displayable name of the extension that may be presented, e.g. in
+ * settings
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String NAME = "name";
+
+ /**
+ * Class name of the Android Activity that contains the settings of the extension
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONFIGURATION_ACTIVITY = "configurationActivity";
+
+ /**
+ * Short text to describe the current configuration state of the extension
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONFIGURATION_TEXT = "configurationText";
+
+ /**
+ * URI of the Android launcher icon representing the extension.
+ * This icon is used by the host application when listing extensions
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String HOST_APP_ICON_URI = "iconLargeUri";
+
+ /**
+ * URI of the icon representing the extension.
+ * This icon is used on the accessory UI.
+ * The size is 36x36 pixels
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ICON_URI = "extensionIconUri";
+
+ /**
+ * URI of the icon representing the extension.
+ * This icon is used on the accessory UI.
+ * The size is 48x48 pixels.
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 2.0
+ */
+ static final String EXTENSION_48PX_ICON_URI = "extension48PxIconUri";
+
+ /**
+ * URI of the monochrome icon representing the extension.
+ * This icon is used on the accessory UI.
+ * The size is 18x18 pixels
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ICON_URI_BLACK_WHITE = "extensionIconUriBlackWhite";
+
+ /**
+ * Used for security reasons for the extension's benefit. If set, this key
+ * will be sent as an extra-data in Intents sent to the extension from
+ * the host application. This enables the extension to verify that the
+ * sender has valid access to the registration content provider.
+ * See section Security
+ * for more information
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_KEY = "extension_key";
+
+ /**
+ * API version. If the extension uses the notification API, this field
+ * should tell what version of the notification API that is used.
+ * Value 0 means that the API is not used
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String NOTIFICATION_API_VERSION = "notificationApiVersion";
+
+ /**
+ * The package name of an extension.
+ * If an extension supports shared user id, the package name
+ * must be specified
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL (REQUIRED if shared user id is used by extension)
+ *
+ *
+ * @since 1.0
+ */
+ static final String PACKAGE_NAME = "packageName";
+
+ /**
+ * Specifies the preferred launch mode for extensions that supports both the
+ * Control and the Notification API.
+ * This value is ignored for extensions that only supports one of these APIs.
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link LaunchMode#CONTROL} (default)
+ * - {@link LaunchMode#NOTIFICATION}
+ *
+ *
+ * PRESENCE: OPTIONAL (REQUIRED if shared user id is used by extension)
+ *
+ *
+ * @since 2.0
+ */
+ static final String LAUNCH_MODE = "launchMode";
+
+ }
+
+ /**
+ * Definitions used for interacting with the ApiRegistration-table
+ *
+ */
+ public interface ApiRegistration {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-registration";
+
+ /**
+ * Path segment
+ */
+ static final String EXTENSIONS_PATH = "registrations";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, EXTENSIONS_PATH);
+ }
+
+ /**
+ * Column-definitions for the ApiRegistration-table
+ */
+ public interface ApiRegistrationColumns extends BaseColumns {
+
+ /**
+ * The ID of the extension corresponding to
+ * this registration
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTENSION_ID = "extensionId";
+
+ /**
+ * Package name name of the Accessory Host Application that
+ * this registration is registered to interact with
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String HOST_APPLICATION_PACKAGE = "hostAppPackageName";
+
+ /**
+ * API version. If the the widget API is used, this field
+ * should tell what version of the widget API that is used.
+ * Value 0 means that the API is not used
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_API_VERSION = "widgetApiVersion";
+
+ /**
+ * API version. If the the control API is used, this field
+ * should tell what version of the control API that is used.
+ * Value 0 means that the API is not used
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_API_VERSION = "controlApiVersion";
+
+ /**
+ * API version. If the the sensor API is used, this field
+ * should tell what version of the sensor API that is used.
+ * Value 0 means that the API is not used
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_API_VERSION = "sensorApiVersion";
+
+ /**
+ * Indicates if the extension (control) supports
+ * Active Low Power. In such cases the extension must
+ * provide a black and white screen to the accessory
+ * This should be done through a XML layout.
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL (Default false)
+ *
+ *
+ * @see Control.Intents#CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT
+ * @see DisplayColumns#SUPPORTS_LOW_POWER_MODE
+ * @since 2.0
+ */
+ static final String LOW_POWER_SUPPORT = "lowPowerSupport";
+
+ /**
+ * If true presses on the {@link Control.KeyCodes#KEYCODE_BACK} will be sent to the Control
+ * extension in {@link Control.Intents#CONTROL_KEY_EVENT_INTENT} intents.
+ * This allows the Control extension to implements its own handling.
+ * If false a press on the {@link Control.KeyCodes#KEYCODE_BACK} will stop the control.
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL (Default false)
+ *
+ *
+ * @since 2.0
+ */
+ static final String CONTROL_BACK_INTERCEPT = "controlBackIntercept";
+ }
+
+ /**
+ * Definitions used for interacting with the Capabilities-view
+ *
+ */
+ public interface Capabilities {
+
+ /**
+ * Data row MIME type for capabilities
+ */
+ static final String CAPABILITIES_MIME_TYPE = "aef-capabilities";
+
+ /**
+ * Path segment capabilities as a separate view
+ */
+ static final String CAPABILITIES_PATH = "capabilities";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, CAPABILITIES_PATH);
+ }
+
+ /**
+ * Definitions used for interacting with the Host application-table
+ *
+ */
+ public interface HostApp {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-host_application";
+
+ /**
+ * Path segment
+ */
+ static final String HOST_APP_PATH = "host_application";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, HOST_APP_PATH);
+ }
+
+ /**
+ * Column-definitions for the Host application table
+ */
+ public interface HostAppColumns extends BaseColumns {
+
+ /**
+ * The package name of a host application
+ *
+ * @since 1.0
+ */
+ static final String PACKAGE_NAME = "packageName";
+
+ /**
+ * The version of a host application
+ *
+ * @since 1.0
+ */
+ static final String VERSION = "version";
+
+ /**
+ * API version. If the host application supports the Widget API, this field
+ * should tell what version of the Widget API that is supported.
+ * Value 0 means that the API is not supported
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_API_VERSION = "widgetApiVersion";
+
+ /**
+ * The maximum supported widget refresh rate
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_REFRESH_RATE = "widgetRefreshrate";
+
+ /**
+ * API version. If the host application supports the Control API, this field
+ * should tell what version of the Control API that is supported.
+ * Value 0 means that the API is not supported
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String CONTROL_API_VERSION = "controlApiVersion";
+
+ /**
+ * API version. If the host application supports the Sensor API, this field
+ * should tell what version of the Sensor API that is supported.
+ * Value 0 means that the API is not supported
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_API_VERSION = "sensorApiVersion";
+
+ /**
+ * API version. If the host application supports the Notification API, this field
+ * should tell what version of the Notification API that is supported.
+ * Value 0 means that the API is not supported
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String NOTIFICATION_API_VERSION = "notificationApiVersion";
+
+ }
+
+ /**
+ * Definitions used for interacting with the Device-table
+ *
+ */
+ public interface Device {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-device";
+
+ /**
+ * Path segment
+ */
+ static final String DEVICES_PATH = "device";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, DEVICES_PATH);
+ }
+
+ /**
+ * Column-definitions for the Device table
+ */
+ public interface DeviceColumns extends BaseColumns {
+
+ /**
+ * The ID of the host application corresponding to
+ * this device
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String HOST_APPLICATION_ID = "hostAppId";
+
+ /**
+ * The device model
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String MODEL = "model";
+
+ /**
+ * The type of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String TYPE = "type";
+
+ /**
+ * The sub-type of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String SUB_TYPE = "subType";
+
+ /**
+ * The marketing name of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String MARKETING_NAME = "marketingName";
+
+ /**
+ * The vendor of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String VENDOR = "vendor";
+
+ /**
+ * The UID of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String UID = "uid";
+
+ /**
+ * The firmware version of the device
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String FIRMWARE_VERSION = "firmwareVersion";
+
+ /**
+ * The height of the widget image
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_IMAGE_HEIGHT = "widgetImageHeight";
+
+ /**
+ * The width of the widget image
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_IMAGE_WIDTH = "widgetImageWidtht";
+
+ /**
+ * Indicates if the device has a vibrator
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String VIBRATOR = "vibrator";
+
+ /**
+ * Indicates if the device is connected to the
+ * host application
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String ACCESSORY_CONNECTED = "accessory_connected";
+
+ /**
+ * Specifies the XML layout elements that are supported on this device.
+ *
+ *
+ * TYPE: INTEGER (int, bit field see {@link LayoutSupport}.
+ *
+ *
+ * PRESENCE: OPTIONAL (Default 0)
+ *
+ *
+ * @see Control.Intents#EXTRA_DATA_XML_LAYOUT
+ * @see Widget.Intents#EXTRA_DATA_XML_LAYOUT
+ *
+ * @since 2.0
+ */
+ static final String LAYOUT_SUPPORT = "layoutSupport";
+ }
+
+ /**
+ * Definitions used for interacting with the Display-table
+ *
+ */
+ public interface Display {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-display";
+
+ /**
+ * Path segment
+ */
+ static final String DISPLAYS_PATH = "display";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, DISPLAYS_PATH);
+ }
+
+
+ /**
+ * Column-definitions for the Display table
+ */
+ public interface DisplayColumns extends BaseColumns {
+
+ /**
+ * The ID of the device corresponding to
+ * this display
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DEVICE_ID = "deviceId";
+
+ /**
+ * The width of the display
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DISPLAY_WIDTH = "width";
+
+ /**
+ * The width of the display
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DISPLAY_HEIGHT = "height";
+
+ /**
+ * The number of colors supported by the display
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String COLORS = "colors";
+
+ /**
+ * The refresh rate supported by the display
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String REFRESH_RATE = "refreshRate";
+
+ /**
+ * The latency of the display
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String LATENCY = "latency";
+
+ /**
+ * Indicates if tap touch is supported by the display
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String TAP_TOUCH = "tapTouch";
+
+ /**
+ * Indicates if motion touch is supported by the display
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String MOTION_TOUCH = "motionTouch";
+
+ /**
+ * Indicates if the display is a real display or an emulated display
+ * to provide compatibility with other accessories.
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL (Default value is FALSE)
+ *
+ *
+ * @since 2.0
+ */
+ static final String IS_EMULATED = "isEmulated";
+
+ /**
+ * Indicates if the display supports active low power mode.
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: OPTIONAL (Default value is FALSE)
+ *
+ *
+ * @since 2.0
+ */
+ static final String SUPPORTS_LOW_POWER_MODE = "supportsLowPowerMode";
+
+ /**
+ * Indicates the number of menu items supported by the display.
+ * 0 means that the accessory does not support showing a menu on this display.
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL (Default value is 0)
+ *
+ *
+ * @since 2.0
+ * @see Control.Intents#CONTROL_MENU_SHOW
+ */
+ static final String MENU_ITEMS = "menuItems";
+ }
+
+ /**
+ * Definitions used for interacting with the Sensor-table
+ *
+ */
+ public interface Sensor {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-sensor";
+
+ /**
+ * Path segment
+ */
+ static final String SENSORS_PATH = "sensor";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, SENSORS_PATH);
+ }
+
+
+ /**
+ * Column-definitions for the Sensor table
+ */
+ public interface SensorColumns extends BaseColumns {
+
+ /**
+ * The ID of the device corresponding to
+ * this sensor
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DEVICE_ID = "deviceId";
+
+ /**
+ * The ID of the SensorType corresponding to
+ * this sensor
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_TYPE_ID = "sensorTypeId";
+
+ /**
+ * The sensor resolution
+ *
+ *
+ * TYPE: REAL (float)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String RESOLUTION = "resolution";
+
+ /**
+ * The minimum delay of the sensor
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String MINIMUM_DELAY = "minimumDelay";
+
+ /**
+ * The maximum range of the sensor
+ *
+ *
+ * TYPE: REAL (float)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String MAXIMUM_RANGE = "maximumRange";
+
+ /**
+ * The name of the sensor
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String NAME = "name";
+
+ /**
+ * The ID of the sensor as defined by the Host Application
+ * this ID is used by the SensorAPI and is not necessarily the same
+ * value as the ID column
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_ID = "sensorId";
+
+ /**
+ * Indicates if the sensor supports interrupt mode
+ * In interrupt mode, the sensor only sends data when new values
+ * are available
+ *
+ *
+ * TYPE: SHORT INTEGER (short) (0= Not supported, 1= Supported)
+ *
+ *
+ * PRESENCE: OPTIONAL
+ *
+ *
+ * @since 1.0
+ */
+ static final String SUPPORTS_SENSOR_INTERRUPT = "sensorInterrupt";
+ }
+
+ /**
+ * Definitions used for interacting with the Led-table
+ *
+ */
+ public interface Led {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-led";
+
+ /**
+ * Path segment
+ */
+ static final String LEDS_PATH = "led";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, LEDS_PATH);
+ }
+
+ /**
+ * Column-definitions for the Led-table
+ */
+ public interface LedColumns extends BaseColumns {
+
+ /**
+ * The ID of the device corresponding to
+ * this LED
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DEVICE_ID = "deviceId";
+
+ /**
+ * The number of colors supported by the LED
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String COLORS = "colors";
+ }
+
+ /**
+ * Definitions used for interacting with the Input-table
+ *
+ */
+ public interface Input {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-input";
+
+ /**
+ * Path segment
+ */
+ static final String INPUTS_PATH = "input";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, INPUTS_PATH);
+ }
+
+ /**
+ * Column-definitions for the Sensor table
+ */
+ public interface InputColumns extends BaseColumns {
+
+ /**
+ * The ID of the device corresponding to
+ * an Input
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DEVICE_ID = "deviceId";
+
+ /**
+ * The ID of the keypad
+ *
+ *
+ * TYPE: INTEGER (long)
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String KEY_PAD_ID = "keyPadId";
+
+ /**
+ * The enable status of the Input
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String ENABLED = "enabled";
+ }
+
+ /**
+ * Definitions used for interacting with the Sensor-type-table
+ *
+ */
+ public interface SensorType {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-sensor_type";
+
+ /**
+ * Path segment
+ */
+ static final String SENSOR_TYPES_PATH = "sensor_type";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, SENSOR_TYPES_PATH);
+ }
+
+ /**
+ * Column-definitions for the SensorType table
+ */
+ public interface SensorTypeColumns extends BaseColumns {
+
+ /**
+ * The Type.
+ *
+ * The following sensor types are supported:
+ *
+ *
+ * - {@link com.sonyericsson.extras.liveware.aef.sensor.Sensor#SENSOR_TYPE_ACCELEROMETER}
+ * - {@link com.sonyericsson.extras.liveware.aef.sensor.Sensor#SENSOR_TYPE_LIGHT}
+ *
+ *
+ *
+ * TYPE: TEXT
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String TYPE = "type";
+
+ /**
+ * This column value indicates whether the sensor
+ * sends information of delicate nature
+ *
+ *
+ * TYPE: BOOLEAN
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String DELICATE_SENSOR_DATA = "delicate_data";
+ }
+
+ /**
+ * Definitions used for interacting with the Keypad-table
+ *
+ */
+ public interface KeyPad {
+ /**
+ * Data row MIME type
+ */
+ static final String MIME_TYPE = "aef-keypad";
+
+ /**
+ * Path segment
+ */
+ static final String KEYPADS_PATH = "keypad";
+
+ /**
+ * Content URI
+ */
+ static final Uri URI = Uri.withAppendedPath(BASE_URI, KEYPADS_PATH);
+ }
+
+ /**
+ * Column-definitions for the Keypad table
+ */
+ public interface KeyPadColumns extends BaseColumns {
+
+ /**
+ * The Type
+ *
+ *
+ * TYPE: TEXT (see {@link KeyPadType for allowed values}
+ *
+ *
+ * PRESENCE: REQUIRED
+ *
+ *
+ * @since 1.0
+ */
+ static final String TYPE = "type";
+ }
+
+ /**
+ * Specifies which API that shall be started when launching the extension.
+ */
+ public interface LaunchMode {
+ /**
+ * The Control API shall be started when launching the extension.
+ *
+ * @since 2.0
+ */
+ static final int CONTROL = 0;
+
+ /**
+ * The Notification API shall be started when launching the extension.
+ *
+ * @since 2.0
+ */
+ static final int NOTIFICATION = 1;
+ }
+
+ /**
+ * Bit field specifiers for supported layout elements.
+ */
+ public interface LayoutSupport {
+ /**
+ * Bit to indicate that android.widget.TextView is supported.
+ *
+ * @since 2.0
+ */
+ static final int TEXT_VIEW = 1;
+
+ /**
+ * Bit to indicate that android.widget.ImageView is supported.
+ *
+ * @since 2.0
+ */
+ static final int IMAGE_VIEW = 1<<1;
+
+ /**
+ * Bit to indicate that android.widget.ListView is supported.
+ *
+ * @since 2.0
+ */
+ static final int LIST_VIEW = 1<<2;
+
+ /**
+ * Bit to indicate that android.widget.Gallery is supported.
+ *
+ * @since 2.0
+ */
+ static final int GALLERY = 1<<3;
+ }
+
+ /**
+ * Key pad type specifiers
+ */
+ public interface KeyPadType {
+
+ /**
+ * Play key. Corresponds to {@link Control.KeyCodes#KEYCODE_PLAY}.
+ */
+ static final String PLAY = "Play";
+
+ /**
+ * Next key. Corresponds to {@link Control.KeyCodes#KEYCODE_NEXT}.
+ */
+ static final String NEXT = "Next";
+
+ /**
+ * Previous key. Corresponds to {@link Control.KeyCodes#KEYCODE_PREVIOUS}.
+ */
+ static final String PREVIOUS = "Previous";
+
+ /**
+ * Volume down key. Corresponds to {@link Control.KeyCodes#KEYCODE_VOLUME_DOWN}.
+ */
+ static final String VOLUME_DOWN = "Volume down";
+
+ /**
+ * Volume up key. Corresponds to {@link Control.KeyCodes#KEYCODE_VOLUME_UP}.
+ */
+ static final String VOLUME_UP = "Volume up";
+
+ /**
+ * Action key. Corresponds to {@link Control.KeyCodes#KEYCODE_ACTION}.
+ */
+ static final String ACTION = "App";
+
+ /**
+ * Back key. Corresponds to {@link Control.KeyCodes#KEYCODE_BACK}.
+ */
+ static final String BACK = "Back";
+ }
+
+ /**
+ * Definitions of sensor types
+ */
+ public interface SensorTypeValue {
+ /**
+ * Constant defining the sensor type Accelerometer.
+ * Sensor data is sent as an array of 3 float values representing
+ * the acceleration on the x-axis, y-axis and z-axis respectively.
+ * All values are in SI units (m/s^2)
+ * For more information about the accelerometer sensor type,
+ * see {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
+ *
+ * @since 1.0
+ */
+ static final String ACCELEROMETER = "Accelerometer";
+
+ /**
+ * Constant defining the sensor type Light.
+ * Sensor data is sent as one float value representing
+ * the light level in SI lux units.
+ * For more information about the light sensor type,
+ * see {@link android.hardware.Sensor#TYPE_LIGHT}
+ *
+ * @since 1.0
+ */
+ static final String LIGHT = "Light";
+
+ /**
+ * Constant defining the sensor type Magnetic Field. Sensor data is sent
+ * as an array of 3 float values representing the ambient geomagnetic
+ * field for all three physical axes (x, y, z) in μT. For more
+ * information about the magnetic field sensor type, see
+ * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD}
+ *
+ * @since 2.0
+ */
+ static final String MAGNETIC_FIELD = "MagneticField";
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/aef/registration/package-info.java b/src/com/sonyericsson/extras/liveware/aef/registration/package-info.java
new file mode 100644
index 0000000..58e10c1
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/registration/package-info.java
@@ -0,0 +1,43 @@
+/*
+Copyright (C) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * The Registration API, part of the Smart Extension APIs for Sony's
+ * smart accessories, defines and implements an Android ContentProvider that
+ * the app extensions can access via the Android ContentResolver API. Before an
+ * app extension can interact with an accessory it must provide (register) some
+ * information needed by the host applications.
+ *
+ * Refer to {@link com.sonyericsson.extras.liveware.aef.registration.Registration} for a detailed description
+ * of the Registration & Capabilities API.
+ */
+package com.sonyericsson.extras.liveware.aef.registration;
+
diff --git a/src/com/sonyericsson/extras/liveware/aef/sensor/Sensor.java b/src/com/sonyericsson/extras/liveware/aef/sensor/Sensor.java
new file mode 100644
index 0000000..4969bb3
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/sensor/Sensor.java
@@ -0,0 +1,366 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (C) 2012-2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.aef.sensor;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+
+/**
+ * Sensor API is a part of the Smart Extension APIs
+ * Overview
+ *
+ * The Sensor API is used to send accessory sensor data from a host application to
+ * an accessory extension.
+ *
+ *
+ * When a host application registers its capabilities, it will declare if it supports
+ * the Sensor API and what sensors it exposes.
+ * Extensions can use the Registration and Capability API to query host
+ * applications for supported sensors.
+ *
+ *
+ * In order to use this API, an application must have registered itself properly in
+ * the registration content provider, see the Registration and Capabilities API.
+ *
+ * How to register a listener
+ *
+ * Sensor data is sent over a LocalSocket. In order to start communication the extension
+ * should setup a LocalServerSocket {@link android.net.LocalServerSocket}
+ * in listening mode and send the {@link Intents#SENSOR_REGISTER_LISTENER_INTENT} intent.
+ * The capability API can be used to query the host application for supported sensors.
+ * If multiple sensors are used, multiple LocalServerSocket objects must also be used since
+ * one sensor is bound to exactly one LocalServerSocket.
+ * When a extension registers a listener it also specifies what sample rate it wants to have.
+ *
+ *
+ * Some sensors support interrupt mode.
+ * They can be configured to only send sensor data when new values are available.
+ * The interrupt flag is part of the sensor data registration Intent.
+ *
+ *
+ * To unregister a listener the extension must send the {@link Intents#SENSOR_UNREGISTER_LISTENER_INTENT}.
+ *
+ * Sensor data format
+ *
+ * The sensor data is sent from the host application over a LocalSocket and can be accessed
+ * through an InputStream.
+ * The data has the following format:
+ *
+ * - Byte 0 - 3 Total length of the data package
+ * - Byte 4 - 7 Accuracy. For more information see {@link SensorAccuracy}
+ * - Byte 8 - 15 Timestamp. The time in nanosecond at which the event happened
+ * - Byte 16 - 19 Length of sensor values in bytes
+ * - Byte 20 - nn Sensor values. This should be interpreted as an array of float values, each float value is 4 bytes
+ *
+ * The length and contents of the values array depends on which sensor type is being monitored.
+ * The data format is identical to a standard Android SensorEvent.
+ *
+ */
+
+public class Sensor {
+
+ /**
+ * @hide
+ * This class is only intended as a utility class containing declared constants
+ * that will be used by Sensor API Extension developers.
+ */
+ protected Sensor() {
+ }
+
+ /**
+ * Intents sent between Sensor Extensions and Accessory Host Applications
+ */
+ public interface Intents {
+
+ /**
+ * Intent used by the Sensor Extension whenever it wants to start listen
+ * to sensor data.
+ * The purpose of the intent is to tell the host application that a
+ * local server socket is now waiting for a connection from the host application.
+ * The name of the local server socket is sent in the intent.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_SENSOR_ID}
+ * - {@link #EXTRA_SENSOR_LOCAL_SERVER_SOCKET_NAME}
+ * - {@link #EXTRA_SENSOR_REQUESTED_RATE}
+ * - {@link #EXTRA_SENSOR_INTERRUPT_MODE}
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_REGISTER_LISTENER_INTENT = "com.sonyericsson.extras.aef.sensor.REGISTER_LISTENER";
+
+ /**
+ * Intent used by the Sensor Extension whenever it wants to stop listen
+ * to sensor data.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_SENSOR_ID}
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_UNREGISTER_LISTENER_INTENT = "com.sonyericsson.extras.aef.sensor.UNREGISTER_LISTENER";
+
+ /**
+ * Intent sent by the Host Application when an error situation has occurred
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_SENSOR_ID}
+ * - {@link #EXTRA_ERROR_CODE}
+ *
+ *
+ * @since 1.0
+ */
+ static final String SENSOR_ERROR_MESSAGE_INTENT = "com.sonyericsson.extras.aef.sensor.ERROR_MESSSAGE";
+
+ /**
+ * The name of the Intent-extra used to identify the Extension.
+ * The extension will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AEA_PACKAGE_NAME = "aea_package_name";
+
+ /**
+ * The name of the Intent-extra used to identify the Sensor.
+ * The Extension will send the ID of the sensor. The ID must
+ * be identical to the value of the SENSOR_ID column from the Sensor
+ * table of a sensor that is attached to the current host application
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SENSOR_ID = "sensor_id";
+
+ /**
+ * The name of the Intent-extra used to identify the name of the Android
+ * Local Server Socket that is now waiting for a connection from
+ * the host application
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SENSOR_LOCAL_SERVER_SOCKET_NAME = "local_server_socket_name";
+
+ /**
+ * The name of the Intent-extra used to set the
+ * preferred delivery rate of the sensor data.
+ * The value must one the predefined constants
+ * {@link SensorRates}
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SENSOR_REQUESTED_RATE = "requested_rate";
+
+ /**
+ * The name of the Intent-extra used to set the
+ * sensor interrupt mode.
+ * The value must one the predefined constants
+ * {@link SensorInterruptMode}
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_SENSOR_INTERRUPT_MODE = "interrupt_mode";
+
+ /**
+ * The name of the Intent-extra used to set the
+ * error code of an error message from the
+ * host application.
+ * The value must be one of the predefined constants
+ * {@link SensorApiErrorCodes}
+ *
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_ERROR_CODE = "error_code";
+
+ /**
+ * The name of the Intent-extra used to identify the Host Application.
+ * The Host Application will send its package name.
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AHA_PACKAGE_NAME = "aha_package_name";
+ }
+
+ /**
+ * Interface used to define constants for
+ * sensor rates.
+ * The extension can chose from one of the
+ * constants defined in this interface.
+ */
+ public interface SensorRates {
+
+ /**
+ * Get sensor data as fast as possible
+ */
+ static final int SENSOR_DELAY_FASTEST = 1;
+
+ /**
+ * Rate suitable for games
+ */
+ static final int SENSOR_DELAY_GAME = 2;
+
+ /**
+ * Rate suitable for screen orientation changes
+ */
+ static final int SENSOR_DELAY_NORMAL = 3;
+
+ /**
+ * Rate suitable for user interface
+ */
+ static final int SENSOR_DELAY_UI = 4;
+ }
+
+ /**
+ * Interface used to define constants for
+ * sensor accuracy.
+ * The accuracy is sent together with the sensor data
+ * over the local socket connection.
+ */
+ public interface SensorAccuracy {
+
+ /**
+ * The values returned by this sensor cannot be trusted,
+ * calibration is needed or the environment will not allow readings
+ */
+ static final int SENSOR_STATUS_UNRELIABLE = 0;
+
+ /**
+ * This sensor is reporting data with low accuracy,
+ * calibration with the environment is needed
+ */
+ static final int SENSOR_STATUS_ACCURACY_LOW = 1;
+
+ /**
+ * This sensor is reporting data with an average level of accuracy,
+ * calibration with the environment may improve the readings
+ */
+ static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2;
+
+ /**
+ * This sensor is reporting data with maximum accuracy
+ */
+ static final int SENSOR_STATUS_ACCURACY_HIGH = 3;
+ }
+
+ /**
+ * Interface used to define constants for
+ * sensor interrupt mode.
+ * The interrupt mode is set when registering a listener
+ */
+ public interface SensorInterruptMode {
+
+ /**
+ * The interrupt mode is disabled,
+ * e.g. the sensor is sending data continuously
+ */
+ static final int SENSOR_INTERRUPT_DISABLED = 0;
+
+ /**
+ * The interrupt mode is enabled,
+ * e.g. no sensor is sent until new sensor data is available
+ */
+ static final int SENSOR_INTERRUPT_ENABLED = 1;
+ }
+
+ /**
+ * Interface used to define constants for
+ * sensor error codes sent from the host application
+ */
+ public interface SensorApiErrorCodes {
+
+ /**
+ * Error code indicating that the action
+ * requested by the extension is not allowed
+ * in the current state
+ */
+ static final int SENSOR_ERROR_CODE_NOT_ALLOWED = 0;
+ }
+
+ /**
+ * Constant defining the sensor type Accelerometer.
+ * Sensor data is sent as an array of 3 float values representing
+ * the acceleration on the x-axis, y-axis and z-axis respectively.
+ * All values are in SI units (m/s^2)
+ * For more information about the accelerometer sensor type,
+ * see {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
+ *
+ * @deprecated
+ * @see Registration.SensorTypeValue#ACCELEROMETER
+ */
+ public static final String SENSOR_TYPE_ACCELEROMETER = Registration.SensorTypeValue.ACCELEROMETER;
+
+ /**
+ * Constant defining the sensor type Light.
+ * Sensor data is sent as one float value representing
+ * the light level in SI lux units.
+ * For more information about the light sensor type,
+ * see {@link android.hardware.Sensor#TYPE_LIGHT}
+ *
+ * @deprecated
+ * @see Registration.SensorTypeValue#LIGHT
+ */
+ public static final String SENSOR_TYPE_LIGHT = Registration.SensorTypeValue.LIGHT;
+}
diff --git a/src/com/sonyericsson/extras/liveware/aef/sensor/package-info.java b/src/com/sonyericsson/extras/liveware/aef/sensor/package-info.java
new file mode 100644
index 0000000..15d80f2
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/sensor/package-info.java
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * The Sensor API, part of the Smart Extension APIs for Sony's
+ * smart accessories, is used to send accessory sensor data from a host
+ * application to an accessory app extension.
+ *
+ * Refer to {@link com.sonyericsson.extras.liveware.aef.sensor.Sensor} for a detailed description
+ * of the Sensor API.
+ */
+package com.sonyericsson.extras.liveware.aef.sensor;
+
diff --git a/src/com/sonyericsson/extras/liveware/aef/widget/Widget.java b/src/com/sonyericsson/extras/liveware/aef/widget/Widget.java
new file mode 100644
index 0000000..a38c5d2
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/widget/Widget.java
@@ -0,0 +1,494 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (C) 2012-2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.aef.widget;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.ExtensionColumns;
+
+/**
+ * The Widget API is a part of the Smart Extension APIs
+ *
+ * Some of our advanced accessories will support the Widget API.
+ * The Widget API enables the Extension to display a live image on the
+ * main menu of the accessory, sort of a preview of what the Extension is about.
+ *
+ * Topics covered here:
+ *
+ * - Initial Widget image
+ *
- How do Extensions find out correct Widget image size
+ *
- Refreshing the Widget image
+ *
+ *
+ *
+ * Initial Widget image
+ *
+ * Widgets will usually be the building blocks for the main menu of the accessories
+ * that support Widgets.
+ * At certain occasions, the Host Application will request a Widget image from the
+ * Extension, {@link Intents#WIDGET_START_REFRESH_IMAGE_INTENT}. E.g. when the user powers on the accessory,
+ * or when a new Widget Extension is installed.
+ * When the Extension receives this Intent, it must send back a Widget image to the Host
+ * Application, {@link Intents#WIDGET_IMAGE_UPDATE_INTENT} or {@link Intents#WIDGET_PROCESS_LAYOUT_INTENT}.
+ * The Extension can continue to update its image when it finds it appropriate as long as the intent
+ * {@link Intents#WIDGET_STOP_REFRESH_IMAGE_INTENT} has not been received. The extension can resume updating
+ * its image when {@link Intents#WIDGET_START_REFRESH_IMAGE_INTENT} has been received again.
+ *
+ *
+ * How do Extensions find out correct Widget image size
+ *
+ * Before an Extension sends the Widget image to the Host Application, it has to figure out
+ * what size the image should be. This might vary between accessories as some have a larger
+ * display. This information can be found using the Registration & Capabilities API.
+ * Every Host Application will write down its parameters into the Capabilities database.
+ *
+ *
+ * Refreshing the Widget image
+ *
+ * In order to allow the user to interact with the Widget, the Extension will get touch events
+ * that occur when your Widget is in focus on the accessory display, {@link Intents#WIDGET_ONTOUCH_INTENT}.
+ * This way, the Extension receives user feedback and can adapt, refresh Widget image.
+ *
+ *
+ * As an example, one could mention a media player controller Widget. The initial Widget image shows a couple
+ * of buttons, play/pause, previous, next, etc. When a user presses somewhere on the Widget, the
+ * {@link Intents#WIDGET_ONTOUCH_INTENT} will be sent to the Extension. Since the Extension provided the
+ * initial image, it knows the exact layout/position of the buttons and can that way determine what button
+ * that was pressed and take action. In this case, it could be to start playing a song. The Extension can
+ * also choose to update the Widget image so that it reflects the latest state, instead of play button,
+ * it might show the pause button and the title of the playing song.
+ *
+
+ */
+
+public class Widget {
+
+ /**
+ * @hide
+ * This class is only intended as a utility class containing declared constants
+ * that will be used by Widget API Extension developers.
+ */
+ protected Widget() {
+ }
+
+ /**
+ * Intents sent between Widget Extensions and Accessory Host Applications.
+ */
+ public interface Intents {
+
+ /**
+ * Intent sent by the Accessory Host Application whenever it wants the Widget to start update it's Widget image.
+ * Usually this Intent will be sent out when the accessory just starts and is about to show the Widget menu.
+ * The Widget image should be updated as soon as possible and after the initial update the Widget image should
+ * be updated occasionally until WIDGET_STOP_REFRESH_IMAGE_INTENT is received
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_START_REFRESH_IMAGE_INTENT = "com.sonyericsson.extras.aef.widget.START_REFRESH_IMAGE_REQUEST";
+
+ /**
+ * Intent sent by the Accessory Host Application whenever it wants the Widget to stop/pause update it's Widget image.
+ * The Widget should resume updating its image when WIDGET_START_REFRESH_IMAGE_INTENT is received.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_STOP_REFRESH_IMAGE_INTENT = "com.sonyericsson.extras.aef.widget.STOP_REFRESH_IMAGE_REQUEST";
+
+
+ /**
+ * Intent sent by the Extension whenever it wants to update the Accessory display by sending an XML layout.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_DATA_XML_LAYOUT}
+ *
+ *
+ * @since 2.0
+ */
+ static final String WIDGET_PROCESS_LAYOUT_INTENT = "com.sonyericsson.extras.aef.widget.PROCESS_LAYOUT";
+
+ /**
+ * Intent used by the Widget Extension whenever it wants to update its widget image.
+ * The Widget image should be updated occasionally.
+ * If the Extension tries to update its Widget image to often, the Host Application will ignore the requests.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_WIDGET_IMAGE_URI}
+ * - {@link #EXTRA_WIDGET_IMAGE_DATA}
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_IMAGE_UPDATE_INTENT = "com.sonyericsson.extras.aef.widget.IMAGE_UPDATE";
+
+ /**
+ * This intent may be used by the Widget Extension as a response to a {@link #WIDGET_ONTOUCH_INTENT}.
+ * The widget should send this intent when it does not want to perform any action based on the on touch intent.
+ * When receiving this intent the host application is free to stop interaction with this widget and enter a new
+ * level or state internally.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_ENTER_NEXT_LEVEL_INTENT = "com.sonyericsson.extras.aef.widget.ENTER_NEW_LEVEL";
+
+ /**
+ * Intent sent by the Host Application to the Widget Extension whenever a user interacts with the Widget image.
+ * Usually as a result of this Intent the Widget Extension will update its Widget image and take appropriate action
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EVENT_TYPE}
+ * - {@link #EXTRA_EVENT_X_POS}
+ * - {@link #EXTRA_EVENT_Y_POS}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ *
+ *
+ * @since 1.0
+ */
+ static final String WIDGET_ONTOUCH_INTENT = "com.sonyericsson.extras.aef.widget.ONTOUCH";
+
+ /**
+ * Intent sent by the Host Application to the Widget Extension whenever an click
+ * event is detected on a graphical object referenced from a layout.
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AHA_PACKAGE_NAME}
+ * - {@link #EXTRA_EVENT_TYPE}
+ * - {@link #EXTRA_EXTENSION_KEY}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ *
+ *
+ * @since 2.0
+ */
+ static final String WIDGET_OBJECT_CLICK_EVENT_INTENT = "com.sonyericsson.extras.aef.widget.OBJECT_CLICK_EVENT";
+
+ /**
+ * Intent sent by the Widget extension whenever it wants to update an image in an ImageView on the accessory.
+ * The image can be a URI or an array of a raw image, like JPEG
+ * The image will replace any previous sent image with the same reference.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_WIDGET_IMAGE_URI}
+ * - {@link #EXTRA_WIDGET_IMAGE_DATA}
+ *
+ *
+ * @since 2.0
+ */
+ static final String WIDGET_SEND_IMAGE_INTENT = "com.sonyericsson.extras.aef.widget.SEND_IMAGE";
+
+ /**
+ * Intent sent by the Widget extension whenever it wants to update a text in a TextView on the accessory.
+ * The text will replace any previous sent text with the same reference.
+ *
+ * This intent should be sent with enforced security by supplying the host application permission
+ * to sendBroadcast(Intent, String). {@link com.sonyericsson.extras.liveware.aef.registration.Registration#HOSTAPP_PERMISSION}
+ *
+ *
+ * Intent-extra data:
+ *
+ *
+ * - {@link #EXTRA_AEA_PACKAGE_NAME}
+ * - {@link #EXTRA_LAYOUT_REFERENCE}
+ * - {@link #EXTRA_WIDGET_TEXT}
+ *
+ *
+ * @since 2.0
+ */
+ static final String WIDGET_SEND_TEXT_INTENT = "com.sonyericsson.extras.aef.widget.SEND_TEXT";
+
+ /**
+ * The name of the Intent-extra used to identify the Host Application.
+ * The Host Application will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AHA_PACKAGE_NAME = "aha_package_name";
+
+ /**
+ * The name of the Intent-extra used to identify the Extension.
+ * The Extension will send its package name
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_AEA_PACKAGE_NAME = "aea_package_name";
+
+ /**
+ * The name of the Intent-extra used to identify the URI of the Widget image.
+ * If the image is in raw data (e.g. an array of bytes) use {@link #EXTRA_WIDGET_IMAGE_DATA} instead.
+ * The image is displayed in the Widget row on the Accessory display.
+ * The image can be updated by the Extension at a later stage
+ *
+ * TYPE: TEXT
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_WIDGET_IMAGE_URI = "widget_image_uri";
+
+ /**
+ * The name of the Intent-extra used to identify the Widget image.
+ * This Intent-extra should be used if the image is in raw data (e.g. an array of bytes).
+ * The image is displayed in the Widget row on the Accessory display.
+ * The image can be updated by the Extension at a later stage
+ *
+ * TYPE: BYTE ARRAY
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_WIDGET_IMAGE_DATA = "widget_image_data";
+
+ /**
+ * The name of the Intent-extra used to identify the touch event
+ *
+ * TYPE: INTEGER (int)
+ *
+ *
+ * ALLOWED VALUES:
+ *
+ * - {@link #EVENT_TYPE_SHORT_TAP}
+ * - {@link #EVENT_TYPE_LONG_TAP}
+ *
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EVENT_TYPE = "widget_event_type";
+
+ /**
+ * The name of the Intent-extra used to carry the X coordinate of the touch event
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EVENT_X_POS = "widget_event_x_pos";
+
+ /**
+ * The name of the Intent-extra used to carry the Y coordinate of the touch event
+ *
+ * TYPE: INTEGER (int)
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EVENT_Y_POS = "widget_event_y_pos";
+
+ /**
+ * The name of the Intent-extra containing the key set by the extension
+ * in {@link ExtensionColumns#EXTENSION_KEY}. This Intent-data is present in
+ * all Intents sent by accessory host application, except where
+ * {@link android.app.Activity#startActivity(android.content.Intent)}
+ * is used. See section Security
+ * for more information
+ *
+ * @since 1.0
+ */
+ static final String EXTRA_EXTENSION_KEY = "extension_key";
+
+ /**
+ * The name of the Intent-extra used to identify the data XML layout to be processed by the host application
+ * and displayed by the accessory.
+ * The layout resource id is used to identify the layout.
+ *
+ * This is a standard Android layout, where a subset of the Android views are supported.
+ *
+ *
+ * Dimensions
+ *
+ * The px dimensions is the only dimension supported.
+ * The only exception is text sizes which can be specified using sp to indicate that the text
+ * shall be scaled according to the text size setting on the accessory.
+ *
+ * ViewGroups
+ *
+ * The following ViewGroups are supported:
+ *
+ * - AbsoluteLayout
+ * - FrameLayout
+ * - LinearLayout
+ * - RelativeLayout
+ *
+ * All XML attributes are supported for the supported ViewGroups.
+ *
+ * Views
+ *
+ * The following Views are supported:
+ *
+ * - View
+ * - ImageView
+ * - TextView
+ *
+ * An accessory may support only a subset of these layouts.
+ * {@link DeviceColumns#LAYOUT_SUPPORT} specifies which Views that are
+ * supported for a certain accessory.
+ *
+ *
+ * The following View XML attributes are supported
+ *
+ * - android:background - restricted to a solid color such as "#ff000000" (black)
+ * - android:clickable - {@link #WIDGET_OBJECT_TOUCH_EVENT_INTENT} are sent for views that are clickable
+ * - android:id
+ * - android:padding
+ * - android:paddingBottom
+ * - android:paddingLeft
+ * - android:paddingRight
+ * - android:paddingTop
+ *
+ *
+ * ImageView
+ *
+ * For an ImageView the following XML attributes are supported
+ *
+ * - android:src - can be a BitmapDrawable or a NinePatchDrawable
+ * - android:scaleType
+ *
+ *
+ * TextView
+ *
+ * For a TextView the following XML attributes are supported
+ *
+ * - android:ellipsize - can be none, start, middle or end
+ * - android:gravity
+ * - android:lines
+ * - android:maxLines
+ * - android:singleLine
+ * - android:text
+ * - android:textColor
+ * - android:textSize - Not all text sizes are supported by all accessories.
+ * If a not supported text size is used the accessory will select the closest available text size.
+ * See the accessory white paper for a list of supported text sizes.
+ *
+ * If the sp unit is used the text is scaled according to settings on the accessories (if supported by the accessory).
+ * If the px unit is used the text is not affected by any settings on the accessory.
+ *
+ * - android:textStyle
+ *
+ *
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_DATA_XML_LAYOUT = "data_xml_layout";
+ /**
+ * The name of the Intent-extra used to identify a reference within an extension.
+ * The extension may use the same reference in multiple layouts.
+ * A reference is a text or an image owned by the extension and will
+ * be used to map between layouts and images/texts.
+ * Corresponds to the android:id XML attribute in the layout.
+ *
+ * TYPE: INTEGER
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_LAYOUT_REFERENCE = "layout_reference";
+
+ /**
+ * The name of the Intent-extra used when sending a text (String)
+ * from the extension to the accessory. The accessory will map the text
+ * to a layout reference.
+ *
+ * TYPE: STRING
+ *
+ * @since 2.0
+ */
+ static final String EXTRA_WIDGET_TEXT = "text_from extension";
+
+ /**
+ * The event type is a short tap.
+ *
+ * @since 1.0
+ */
+ static final int EVENT_TYPE_SHORT_TAP = 0;
+
+ /**
+ * The event type is a long tap
+ *
+ * @since 1.0
+ */
+ static final int EVENT_TYPE_LONG_TAP = 1;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/aef/widget/package-info.java b/src/com/sonyericsson/extras/liveware/aef/widget/package-info.java
new file mode 100644
index 0000000..ad33fa6
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/aef/widget/package-info.java
@@ -0,0 +1,41 @@
+/*
+Copyright (C) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/**
+ * The Widget API, part of the Smart Extension APIs for Sony's
+ * smart accessories, enables the app extension to display a live image on the
+ * main menu of the accessory (like a preview of the app extension itself).
+ *
+ * Refer to {@link com.sonyericsson.extras.liveware.aef.widget.Widget} for a detailed description
+ * of the Widget API.
+ */
+package com.sonyericsson.extras.liveware.aef.widget;
+
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/AefTextView.java b/src/com/sonyericsson/extras/liveware/extension/util/AefTextView.java
new file mode 100644
index 0000000..6c3904c
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/AefTextView.java
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/**
+ * This extension of TextView renders text on a single line, and fades end if
+ * text does not fit. NOTE! This view only supports a single text line and only
+ * handles gravity left.
+ */
+public class AefTextView extends TextView {
+ public AefTextView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public AefTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Force single line, see also setHorizontalFadingEdgeEnabled below
+ setHorizontallyScrolling(true);
+
+ // remove new lines if any
+ String text = getText().toString();
+ text = text.replaceAll("\\r?\\n", " ");
+ setText(text);
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ float textWidth = getPaint().measureText(getText().toString());
+ float availableWidth = getMeasuredWidth();
+
+ // fade if too long text
+ setHorizontalFadingEdgeEnabled(textWidth > availableWidth);
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/Dbg.java b/src/com/sonyericsson/extras/liveware/extension/util/Dbg.java
new file mode 100644
index 0000000..1d19ac2
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/Dbg.java
@@ -0,0 +1,92 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import android.util.Log;
+
+public final class Dbg {
+
+ public static final boolean DEBUG = true;
+
+ private static String LOG_TAG = "ExtensionUtils";
+
+ private Dbg() {
+ }
+
+ private static boolean logEnabled() {
+ if (DEBUG) {
+ return Log.isLoggable(LOG_TAG, Log.DEBUG);
+ } else {
+ return false;
+ }
+ }
+
+ public static void v(String s) {
+ if (logEnabled()) {
+ android.util.Log.v(LOG_TAG, s);
+ }
+ }
+
+ public static void e(String s) {
+ if (logEnabled()) {
+ android.util.Log.e(LOG_TAG, s);
+ }
+ }
+
+ public static void e(String s, Throwable t) {
+ if (logEnabled()) {
+ android.util.Log.e(LOG_TAG, s, t);
+ }
+ }
+
+ public static void w(String s) {
+ if (logEnabled()) {
+ android.util.Log.w(LOG_TAG, s);
+ }
+ }
+
+ public static void w(String s, Throwable t) {
+ if (logEnabled()) {
+ android.util.Log.w(LOG_TAG, s, t);
+ }
+ }
+
+ public static void d(String s) {
+ if (logEnabled()) {
+ android.util.Log.d(LOG_TAG, s);
+ }
+ }
+
+ public static void setLogTag(final String tag) {
+ LOG_TAG = tag;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/ExtensionService.java b/src/com/sonyericsson/extras/liveware/extension/util/ExtensionService.java
new file mode 100644
index 0000000..0a6fa38
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/ExtensionService.java
@@ -0,0 +1,899 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+Copyright (c) 2012 Sony Mobile Communications AB.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import android.app.Service;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Device;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+import com.sonyericsson.extras.liveware.aef.widget.Widget;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlExtension;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlListItem;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlObjectClickEvent;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlTouchEvent;
+import com.sonyericsson.extras.liveware.extension.util.registration.IRegisterCallback;
+import com.sonyericsson.extras.liveware.extension.util.registration.RegisterExtensionTask;
+import com.sonyericsson.extras.liveware.extension.util.registration.RegistrationInformation;
+import com.sonyericsson.extras.liveware.extension.util.widget.WidgetExtension;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * The extension service is an abstract class that should be extended for
+ * accessory extensions.
+ */
+public abstract class ExtensionService extends Service implements IRegisterCallback {
+
+ private class IntentRunner implements Runnable {
+ protected Intent mIntent;
+ protected int mRunnerStartId;
+
+ IntentRunner(Intent intent, int startId) {
+ mIntent = intent;
+ mRunnerStartId = startId;
+ }
+
+ /**
+ * Does nothing should be overridden
+ */
+ @Override
+ public void run() {
+ }
+ }
+
+ public static final int INVALID_ID = -1;
+
+ private RegisterExtensionTask mRegisterTask = null;
+
+ private final String mExtensionKey;
+
+ private RegistrationInformation mRegistrationInformation;
+
+ private HashMap mWidgets = new HashMap();
+
+ private HashMap mControls = new HashMap();
+
+ private int mStartId;
+
+ private Handler mHandler;
+
+ private boolean mPendingNewRegistration = false;
+
+ private boolean mUpdateSourceRegistration = true;
+
+ /**
+ * Create instance of ExtensionService
+ *
+ * @param extensionKey The extension key.
+ */
+ public ExtensionService(String extensionKey) {
+ if (extensionKey == null) {
+ throw new IllegalArgumentException("extensionKey == null");
+ }
+ mExtensionKey = extensionKey;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see android.app.Service#onCreate()
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Get registration information
+ mRegistrationInformation = getRegistrationInformation();
+ if (mRegistrationInformation == null) {
+ throw new IllegalArgumentException("registrationInformation == null");
+ }
+
+ mUpdateSourceRegistration = mRegistrationInformation
+ .isSourcesToBeUpdatedAtServiceCreation();
+ mHandler = new Handler();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null) {
+ IntentRunner runner = new IntentRunner(intent, startId) {
+ @Override
+ public void run() {
+ ExtensionService.this.mStartId = mRunnerStartId;
+ String action = mIntent.getAction();
+ if (Registration.Intents.EXTENSION_REGISTER_REQUEST_INTENT.equals(action)) {
+ onRegisterRequest();
+ // Registration done in async task.
+ // Stopped when task is completed
+ } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ onLocaleChanged();
+ stopSelfCheck();
+ } else if (Registration.Intents.ACCESSORY_CONNECTION_INTENT.equals(action)) {
+ int status = mIntent.getIntExtra(
+ Registration.Intents.EXTRA_CONNECTION_STATUS, -1);
+ onConnectionChanged(status == Registration.AccessoryConnectionStatus.STATUS_CONNECTED);
+ if (status == Registration.AccessoryConnectionStatus.STATUS_DISCONNECTED) {
+ // Accessory disconnected.
+ stopSelfCheck();
+ } else {
+ stopSelfCheck(true);
+ }
+ } else if (Notification.Intents.VIEW_EVENT_INTENT.equals(action)
+ || Notification.Intents.REFRESH_REQUEST_INTENT.equals(action)) {
+ handleNotificationIntent(mIntent);
+ // Check if service shall be stopped.
+ // Assume accessory connected as it sent something to
+ // us.
+ stopSelfCheck(true);
+ } else if (Widget.Intents.WIDGET_START_REFRESH_IMAGE_INTENT.equals(action)
+ || Widget.Intents.WIDGET_STOP_REFRESH_IMAGE_INTENT.equals(action)
+ || Widget.Intents.WIDGET_ONTOUCH_INTENT.equals(action)
+ || Widget.Intents.WIDGET_OBJECT_CLICK_EVENT_INTENT.equals(action)
+ || WidgetExtension.SCHEDULED_REFRESH_INTENT.equals(action)) {
+ handleWidgetIntent(mIntent);
+
+ // Check if service shall be stopped.
+ // Assume accessory connected as it sent something to
+ // us.
+ stopSelfCheck(true);
+ } else if (Control.Intents.CONTROL_START_INTENT.equals(action)
+ || Control.Intents.CONTROL_STOP_INTENT.equals(action)
+ || Control.Intents.CONTROL_RESUME_INTENT.equals(action)
+ || Control.Intents.CONTROL_PAUSE_INTENT.equals(action)
+ || Control.Intents.CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT
+ .equals(action)
+ || Control.Intents.CONTROL_ERROR_INTENT.equals(action)
+ || Control.Intents.CONTROL_KEY_EVENT_INTENT.equals(action)
+ || Control.Intents.CONTROL_TOUCH_EVENT_INTENT.equals(action)
+ || Control.Intents.CONTROL_OBJECT_CLICK_EVENT_INTENT.equals(action)
+ || Control.Intents.CONTROL_LIST_REQUEST_ITEM_INTENT.equals(action)
+ || Control.Intents.CONTROL_LIST_ITEM_CLICK_INTENT.equals(action)
+ || Control.Intents.CONTROL_LIST_REFRESH_REQUEST_INTENT.equals(action)
+ || Control.Intents.CONTROL_LIST_ITEM_SELECTED_INTENT.equals(action)
+ || Control.Intents.CONTROL_MENU_ITEM_SELECTED.equals(action)
+ || Control.Intents.CONTROL_SWIPE_EVENT_INTENT.equals(action)) {
+ handleControlIntent(mIntent);
+ // Check if service shall be stopped.
+ // Assume accessory connected as it sent something to
+ // us.
+ stopSelfCheck(true);
+ }
+ }
+ };
+ // post on handler to return quicker since started from broadcast
+ // receiver
+ mHandler.post(runner);
+ }
+
+ return START_STICKY;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see android.app.Service#onDestroy()
+ */
+ @Override
+ public void onDestroy() {
+ if (mRegisterTask != null) {
+ mRegisterTask.setRegisterInterface(null);
+ mRegisterTask.cancel(true);
+ mRegisterTask = null;
+ }
+
+ destroyAllWidgets();
+ destroyAllControls();
+
+ super.onDestroy();
+ }
+
+ /**
+ * Perform extension registration in background Override this method to do
+ * anything else when locale change
+ *
+ * @see #onRegisterResult()
+ */
+ protected void onLocaleChanged() {
+ registerOrUpdate(false);
+ }
+
+ /**
+ * Called when accessory is connected/disconnected Override this method to
+ * handle connection/disconnection of accessory
+ *
+ * @param success True on source registration refresh success.
+ */
+ protected void onConnectionChanged(boolean connected) {
+
+ }
+
+ /**
+ * Perform extension registration in background Override this method to
+ * handle registration
+ *
+ * @see #onRegisterResult()
+ */
+ protected void onRegisterRequest() {
+ registerOrUpdate(false);
+ }
+
+ /**
+ * Perform extension registration in background.
+ *
+ * @param onlySources True if only sources shall be refreshed. False for
+ * full registration update.
+ * @see #onRegisterResult()
+ */
+ protected void registerOrUpdate(boolean onlySources) {
+ mUpdateSourceRegistration = false;
+
+ if (mRegisterTask != null) {
+ if (Dbg.DEBUG) {
+ Dbg.d("Registration already on-going. Queueing new request.");
+ }
+ // New registration request received when we are already busy
+ // handling a registration request.
+ // Run this registration when we have finished the first one.
+ // This to handle the case when a host app installs when we
+ // are busy registering ourselves.
+ mPendingNewRegistration = true;
+ return;
+ }
+
+ mRegisterTask = new RegisterExtensionTask(this, mRegistrationInformation, this, onlySources);
+ mRegisterTask.execute();
+ mPendingNewRegistration = false;
+ }
+
+ @Override
+ public final void onExtensionRegisterResult(boolean onlySources, boolean success) {
+ mRegisterTask = null;
+
+ if (mPendingNewRegistration) {
+ registerOrUpdate(false);
+ } else {
+ // Notify extension
+ if (onlySources) {
+ onSourceRefreshResult(success);
+ } else {
+ onRegisterResult(success);
+ }
+
+ // Check if the service shall be run or shall be stopped.
+ stopSelfCheck();
+ }
+ }
+
+ /**
+ * Called after extension registration Override this method if you want to
+ * take actions after registration.
+ *
+ * @param result True on register success, false otherwise
+ */
+ public void onRegisterResult(boolean success) {
+
+ }
+
+ /**
+ * Called after source registration has been refreshed. Override this method
+ * if you want to take actions after a source registration refresh.
+ *
+ * @param success True on source registration refresh success.
+ */
+ public void onSourceRefreshResult(boolean success) {
+
+ }
+
+ /**
+ * Handle VIEW_EVENT_INTENT. Override this method if this is a notification
+ * extension
+ *
+ * @see #getRequiredNotificationApiVersion()
+ * @param intent The view intent
+ */
+ protected void onViewEvent(Intent intent) {
+
+ }
+
+ /**
+ * Handle REFRESH_REQUEST_INTENT. Sync extension data in this callback. This
+ * is only relevant for extensions that aren't always up to date, like
+ * polling extensions. Override this method if this is a notification
+ * extension
+ *
+ * @see #getRequiredNotificationApiVersion()
+ */
+ protected void onRefreshRequest() {
+
+ }
+
+ /**
+ * Get the extension registration information
+ *
+ * @return The extension registration information.
+ */
+ protected abstract RegistrationInformation getRegistrationInformation();
+
+ /**
+ * Shall the extension service be kept running as long as an accessory is
+ * connected.
+ *
+ * @return True if the service shall be kept running as long an accessory is
+ * connected.
+ */
+ protected abstract boolean keepRunningWhenConnected();
+
+ /**
+ * Stop the service if there is no activities that requires the service to
+ * be running.
+ *
+ * @param accessoryConnected False if accessory is not connected.
+ */
+ private final void stopSelfCheck(boolean accessoryConnected) {
+ if (Dbg.DEBUG) {
+ Dbg.d("stopSelfCheck: " + accessoryConnected);
+ }
+
+ if (mRegisterTask != null) {
+ // Registration on-going do not stop.
+ if (Dbg.DEBUG) {
+ Dbg.d("registration on-going not stopping");
+ }
+ return;
+ }
+
+ if (mUpdateSourceRegistration) {
+ // The source registration shall be refreshed.
+ registerOrUpdate(true);
+ return; // Not stopping because we just started a registration.
+ }
+
+ if (!accessoryConnected) {
+ // No accessory connected. Stop service.
+ // There is little point in doing anything if there
+ // is no accessory connected.
+ stopSelf(mStartId);
+ return;
+ }
+
+ if (mWidgets.size() > 0) {
+ // Widget is visible. Do not stop service.
+ if (Dbg.DEBUG) {
+ Dbg.d("widget is visible. Not stopping");
+ }
+ return;
+ }
+
+ if (mControls.size() > 0) {
+ // Control is visible. Do not stop service.
+ if (Dbg.DEBUG) {
+ Dbg.d("control is visible. Not stopping");
+ }
+ return;
+ }
+
+ if (!keepRunningWhenConnected()) {
+ // If the extension does not require that the service is
+ // running when there is an accessory connected we may stop it now.
+ stopSelf(mStartId);
+ return;
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.d("keep running when connected. Not stopping");
+ }
+ }
+ }
+
+ /**
+ * Stop the service if there is no activities that requires the service to
+ * be running.
+ */
+ protected final void stopSelfCheck() {
+ stopSelfCheck(areAnyAccessoriesConnected());
+ }
+
+ /**
+ * Create widget extension. Override this method to provide extension
+ * widgets.
+ *
+ * @param hostAppPackageName The host application package name.
+ * @see WidgetExtension
+ * @see RegistrationInformation#getRequiredWidgetApiVersion()
+ * @return The widget extension.
+ */
+ public WidgetExtension createWidgetExtension(String hostAppPackageName) {
+ throw new IllegalArgumentException(
+ "createWidgetExtension() not implemented. Widget extensions must override this method");
+ }
+
+ /**
+ * Trigger action for all widgets. Use this method to take the same action
+ * on all widgets.
+ *
+ * @param requestCode Code defined by the caller used to distinguish between
+ * different actions.
+ * @param bundle Optional bundle with additional information.
+ */
+ public void doActionOnAllWidgets(int requestCode, Bundle bundle) {
+ Iterator iterator = mWidgets.values().iterator();
+ while (iterator.hasNext()) {
+ WidgetExtension widget = iterator.next();
+ widget.onDoAction(requestCode, bundle);
+ }
+ }
+
+ /**
+ * Trigger action on a widget.
+ *
+ * @param requestCode Code defined by the caller used to distinguish between
+ * different actions.
+ * @param hostAppPackageName The host app package name.
+ * @param bundle Optional bundle with additional information.
+ * @returns True if the widget exists. False otherwise.
+ */
+ public boolean doActionOnWidget(int requestCode, String hostAppPackageName, Bundle bundle) {
+ WidgetExtension widget = mWidgets.get(hostAppPackageName);
+ if (widget != null) {
+ widget.onDoAction(requestCode, bundle);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Destroy all widgets. Inform all widgets that they shall free any
+ * resources such as threads and registered broad cast receivers.
+ */
+ public void destroyAllWidgets() {
+
+ Iterator iterator = mWidgets.values().iterator();
+ while (iterator.hasNext()) {
+ WidgetExtension widget = iterator.next();
+ widget.destroy();
+ }
+ }
+
+ /**
+ * Create control extension. Override this method to provide extension
+ * control.
+ *
+ * @param hostAppPackageName The host application package name.
+ * @see RegistrationInformation#getRequiredControlApiVersion()
+ * @see ControlExtension
+ * @return The control extension.
+ */
+ public ControlExtension createControlExtension(String hostAppPackageName) {
+ throw new IllegalArgumentException(
+ "createControlExtension() not implemented. Control extensions must override this method");
+ }
+
+ /**
+ * Trigger action for all controls. Use this method to take the same action
+ * on all controls.
+ *
+ * @param requestCode Code defined by the caller used to distinguish between
+ * different actions.
+ * @param bundle Optional bundle with additional information.
+ */
+ public void doActionOnAllControls(int requestCode, Bundle bundle) {
+ Iterator iterator = mControls.values().iterator();
+ while (iterator.hasNext()) {
+ ControlExtension control = iterator.next();
+ control.onDoAction(requestCode, bundle);
+ }
+ }
+
+ /**
+ * Trigger action on a control.
+ *
+ * @param requestCode Code defined by the caller used to distinguish between
+ * different actions.
+ * @param hostAppPackageName The host app package name.
+ * @param bundle Optional bundle with additional information.
+ * @returns True if the widget exists. False otherwise.
+ */
+ public boolean doActionOnControl(int requestCode, String hostAppPackageName, Bundle bundle) {
+ ControlExtension control = mControls.get(hostAppPackageName);
+ if (control != null) {
+ control.onDoAction(requestCode, bundle);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Destroy all controls. Inform all controls that they shall free any
+ * resources such as threads and registered broad cast receivers.
+ */
+ public void destroyAllControls() {
+ Iterator iterator = mControls.values().iterator();
+ while (iterator.hasNext()) {
+ ControlExtension control = iterator.next();
+ control.destroy();
+ }
+ }
+
+ /**
+ * Handle notification intent.
+ *
+ * @param intent The intent to handle.
+ */
+ private final void handleNotificationIntent(final Intent intent) {
+ String action = intent.getAction();
+
+ if (!mExtensionKey.equals(intent.getStringExtra(Notification.Intents.EXTRA_EXTENSION_KEY))) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Invalid extension key: "
+ + intent.getStringExtra(Notification.Intents.EXTRA_EXTENSION_KEY));
+ }
+ return;
+ }
+
+ if (Notification.Intents.VIEW_EVENT_INTENT.equals(action)) {
+ onViewEvent(intent);
+ } else if (Notification.Intents.REFRESH_REQUEST_INTENT.equals(action)) {
+ onRefreshRequest();
+ }
+ }
+
+ /**
+ * Handle widget intent.
+ *
+ * @param intent The intent to handle.
+ */
+ private final void handleWidgetIntent(final Intent intent) {
+ String action = intent.getAction();
+ if (Dbg.DEBUG) {
+ Dbg.d("Received intent: " + action);
+ }
+
+ if (!mExtensionKey.equals(intent.getStringExtra(Widget.Intents.EXTRA_EXTENSION_KEY))) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Invalid extension key: "
+ + intent.getStringExtra(Widget.Intents.EXTRA_EXTENSION_KEY));
+ }
+ return;
+ }
+
+ String hostAppPackageName = intent.getStringExtra(Widget.Intents.EXTRA_AHA_PACKAGE_NAME);
+
+ // Lookup widget based on host application package name.
+ WidgetExtension widget = mWidgets.get(hostAppPackageName);
+
+ if (widget == null) {
+ if (Widget.Intents.WIDGET_STOP_REFRESH_IMAGE_INTENT.equals(action)) {
+ if (Dbg.DEBUG) {
+ Dbg.w("No widget object for: " + hostAppPackageName + ". Ignoring stop.");
+ }
+ return;
+ }
+ if (WidgetExtension.SCHEDULED_REFRESH_INTENT.equals(action)) {
+ // Don't create new widget object for scheduled refresh.
+ // If the widget was stopped, but there was a scheduled refresh
+ // waiting to be processed this would start the widget again.
+ if (Dbg.DEBUG) {
+ Dbg.d("No widget object for: " + hostAppPackageName
+ + ". Ignoring scheduled refersh.");
+ }
+ return;
+ }
+
+ if (!Widget.Intents.WIDGET_START_REFRESH_IMAGE_INTENT.equals(action)) {
+ if (Dbg.DEBUG) {
+ Dbg.w("No widget object for: " + hostAppPackageName + ". Creating one.");
+ }
+ }
+
+ // Create new widget and add it to the list of active widgets.
+
+ // We do this not only for start intents since the process might be
+ // killed after the start intent, and in that case we need to
+ // recreate the widget object when we get a new intent.
+ // Otherwise it will be experienced as the that the widget is not
+ // responding to user actions.
+ widget = createWidgetExtension(hostAppPackageName);
+ mWidgets.put(hostAppPackageName, widget);
+
+ widget.startRefresh();
+ } else {
+ if (Widget.Intents.WIDGET_START_REFRESH_IMAGE_INTENT.equals(action)) {
+ // ignoring start for already started.
+ if (Dbg.DEBUG) {
+ Dbg.w("Ignoring start for: " + hostAppPackageName + ". Already started.");
+ }
+ }
+ }
+
+ if (Widget.Intents.WIDGET_STOP_REFRESH_IMAGE_INTENT.equals(action)) {
+ widget.stopRefresh();
+
+ // Destroy the widget and remove it from the list of active
+ // widgets.
+ widget.destroy();
+ mWidgets.remove(hostAppPackageName);
+
+ } else if (WidgetExtension.SCHEDULED_REFRESH_INTENT.equals(action)) {
+ widget.onScheduledRefresh();
+ } else if (Widget.Intents.WIDGET_ONTOUCH_INTENT.equals(action)) {
+ int type = intent.getIntExtra(Widget.Intents.EXTRA_EVENT_TYPE, -1);
+ int x = intent.getIntExtra(Widget.Intents.EXTRA_EVENT_X_POS, -1);
+ int y = intent.getIntExtra(Widget.Intents.EXTRA_EVENT_Y_POS, -1);
+ if (Dbg.DEBUG) {
+ Dbg.v("Widget on touch type: " + type + " x: " + x + " y: " + y);
+ }
+ if (type == -1) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Invalid type: " + type);
+ }
+ return;
+ }
+ if (x == -1) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Invalid x pos: " + x);
+ }
+ return;
+ }
+ if (y == -1) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Invalid y pos: " + y);
+ }
+ return;
+ }
+
+ widget.onTouch(type, x, y);
+ } else if (Widget.Intents.WIDGET_OBJECT_CLICK_EVENT_INTENT.equals(action)) {
+ int type = intent.getIntExtra(Widget.Intents.EXTRA_EVENT_TYPE, -1);
+ int layoutReference = intent.getIntExtra(Widget.Intents.EXTRA_LAYOUT_REFERENCE, -1);
+ widget.onObjectClick(type, layoutReference);
+ }
+
+ }
+
+ /**
+ * Handle control intent.
+ *
+ * @param intent The intent to handle.
+ */
+ private final void handleControlIntent(final Intent intent) {
+
+ if (!mExtensionKey.equals(intent.getStringExtra(Control.Intents.EXTRA_EXTENSION_KEY))) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Invalid extension key: "
+ + intent.getStringExtra(Control.Intents.EXTRA_EXTENSION_KEY));
+ }
+ return;
+ }
+
+ String action = intent.getAction();
+ String hostAppPackageName = intent.getStringExtra(Control.Intents.EXTRA_AHA_PACKAGE_NAME);
+
+ // Lookup control based on host application package name.
+ ControlExtension control = mControls.get(hostAppPackageName);
+ if (control == null) {
+ if (Control.Intents.CONTROL_STOP_INTENT.equals(action)) {
+ if (Dbg.DEBUG) {
+ Dbg.w("No control object for: " + hostAppPackageName + ". Ignoring stop.");
+ }
+ return;
+ } else if (Control.Intents.CONTROL_ERROR_INTENT.equals(action)) {
+ onControlError(hostAppPackageName,
+ intent.getIntExtra(Control.Intents.EXTRA_ERROR_CODE, -1));
+ return;
+ }
+
+ // Create new control and add it to the list of active controls.
+
+ // We do this not only for start intents since the process might be
+ // killed after the start intent, and in that case we need to
+ // recreate the control object when we get a new intent.
+ // Otherwise it will be experienced as the that the control is not
+ // responding to user actions.
+ control = createControlExtension(hostAppPackageName);
+ mControls.put(hostAppPackageName, control);
+
+ control.start();
+
+ if (!Control.Intents.CONTROL_START_INTENT.equals(action)) {
+ if (Dbg.DEBUG) {
+ Dbg.w("No control object for: " + hostAppPackageName + ". Creating one.");
+ }
+
+ if (!Control.Intents.CONTROL_PAUSE_INTENT.equals(action)) {
+ // If it wasn't a pause intent then assume that the control
+ // is
+ // foreground and also call resume.
+ if (Dbg.DEBUG) {
+ Dbg.w("Calling faked resume");
+ }
+ control.resume();
+ }
+ }
+
+ } else {
+ if (Control.Intents.CONTROL_START_INTENT.equals(action)) {
+ // Ignoring start for already started.
+ if (Dbg.DEBUG) {
+ Dbg.w("Ignoring start for: " + hostAppPackageName + ". Already started.");
+ }
+ }
+ }
+
+ if (Control.Intents.CONTROL_STOP_INTENT.equals(action)) {
+ control.stop();
+
+ // Destroy the control and remove it from the list of active
+ // controls.
+ control.destroy();
+ mControls.remove(hostAppPackageName);
+ } else if (Control.Intents.CONTROL_RESUME_INTENT.equals(action)) {
+ control.resume();
+ } else if (Control.Intents.CONTROL_PAUSE_INTENT.equals(action)) {
+ control.pause();
+ } else if (Control.Intents.CONTROL_ERROR_INTENT.equals(action)) {
+ control.onError(intent.getIntExtra(Control.Intents.EXTRA_ERROR_CODE, -1));
+ } else if (Control.Intents.CONTROL_KEY_EVENT_INTENT.equals(action)) {
+ control.onKey(intent.getIntExtra(Control.Intents.EXTRA_KEY_ACTION, -1),
+ intent.getIntExtra(Control.Intents.EXTRA_KEY_CODE, -1),
+ intent.getLongExtra(Control.Intents.EXTRA_TIMESTAMP, 0));
+ } else if (Control.Intents.CONTROL_TOUCH_EVENT_INTENT.equals(action)) {
+
+ ControlTouchEvent event = new ControlTouchEvent(intent.getIntExtra(
+ Control.Intents.EXTRA_TOUCH_ACTION, -1), intent.getLongExtra(
+ Control.Intents.EXTRA_TIMESTAMP, 0), intent.getIntExtra(
+ Control.Intents.EXTRA_X_POS, -1), intent.getIntExtra(
+ Control.Intents.EXTRA_Y_POS, -1));
+
+ control.onTouch(event);
+ } else if (Control.Intents.CONTROL_SWIPE_EVENT_INTENT.equals(action)) {
+ control.onSwipe(intent.getIntExtra(Control.Intents.EXTRA_SWIPE_DIRECTION, -1));
+ } else if (Control.Intents.CONTROL_OBJECT_CLICK_EVENT_INTENT.equals(action)) {
+
+ ControlObjectClickEvent event = new ControlObjectClickEvent(intent.getIntExtra(
+ Control.Intents.EXTRA_CLICK_TYPE, -1), intent.getLongExtra(
+ Control.Intents.EXTRA_TIMESTAMP, 0), intent.getIntExtra(
+ Control.Intents.EXTRA_LAYOUT_REFERENCE, -1));
+
+ control.onObjectClick(event);
+ } else if (Control.Intents.CONTROL_LIST_REQUEST_ITEM_INTENT.equals(action)) {
+ control.onRequestListItem(
+ intent.getIntExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, -1),
+ intent.getIntExtra(Control.Intents.EXTRA_LIST_ITEM_POSITION, -1));
+ } else if (Control.Intents.CONTROL_LIST_ITEM_CLICK_INTENT.equals(action)) {
+ ControlListItem listItem = new ControlListItem();
+ listItem.layoutReference = intent.getIntExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE,
+ -1);
+ listItem.listItemId = intent.getIntExtra(Control.Intents.EXTRA_LIST_ITEM_ID, -1);
+ listItem.listItemPosition = intent.getIntExtra(
+ Control.Intents.EXTRA_LIST_ITEM_POSITION, -1);
+
+ control.onListItemClick(listItem,
+ intent.getIntExtra(Control.Intents.EXTRA_CLICK_TYPE, -1),
+ intent.getIntExtra(Control.Intents.EXTRA_LIST_ITEM_LAYOUT_REFERENCE, -1));
+ } else if (Control.Intents.CONTROL_LIST_ITEM_SELECTED_INTENT.equals(action)) {
+ ControlListItem listItem = new ControlListItem();
+ listItem.layoutReference = intent.getIntExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE,
+ -1);
+ listItem.listItemId = intent.getIntExtra(Control.Intents.EXTRA_LIST_ITEM_ID, -1);
+ listItem.listItemPosition = intent.getIntExtra(
+ Control.Intents.EXTRA_LIST_ITEM_POSITION, -1);
+
+ control.onListItemSelected(listItem);
+ } else if (Control.Intents.CONTROL_LIST_REFRESH_REQUEST_INTENT.equals(action)) {
+ Dbg.d("List refresh");
+ } else if (Control.Intents.CONTROL_MENU_ITEM_SELECTED.equals(action)) {
+ control.onMenuItemSelected(intent.getIntExtra(Control.Intents.EXTRA_MENU_ITEM_ID, -1));
+ } else if (Control.Intents.CONTROL_ACTIVE_POWER_SAVE_MODE_STATUS_CHANGED_INTENT
+ .equals(action)) {
+ int powerState = intent.getIntExtra(Control.Intents.EXTRA_ACTIVE_POWER_MODE_STATUS,
+ Control.Intents.ACTIVE_POWER_SAVE_MODE_OFF);
+ control.onActiveLowPowerModeChange(powerState == Control.Intents.ACTIVE_POWER_SAVE_MODE_ON);
+ }
+ }
+
+ /**
+ * Check in the database if there are any accessories connected.
+ *
+ * @return True if at least one accessories is connected.
+ */
+ protected boolean areAnyAccessoriesConnected() {
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(Device.URI, null,
+ DeviceColumns.ACCESSORY_CONNECTED + " = 1", null, null);
+ if (cursor != null) {
+ return (cursor.getCount() > 0);
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Send control start request.
+ *
+ * @param hostAppPackageName The host application package to start a control
+ * for.
+ */
+ protected void controlStartRequest(String hostAppPackageName) {
+ Intent intent = new Intent(Control.Intents.CONTROL_START_REQUEST_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_AEA_PACKAGE_NAME, getPackageName());
+ intent.setPackage(hostAppPackageName);
+ sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);
+ }
+
+ /**
+ * A control error occurred and there is no control started for the host
+ * application.
+ *
+ * @param hostAppPackageName The host application package name.
+ * @param errorCode The error code. {@link Control.Intents#EXTRA_ERROR_CODE}
+ */
+ protected void onControlError(String hostAppPackageName, int errorCode) {
+
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/ExtensionUtils.java b/src/com/sonyericsson/extras/liveware/extension/util/ExtensionUtils.java
new file mode 100644
index 0000000..47643aa
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/ExtensionUtils.java
@@ -0,0 +1,437 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Device;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.text.TextPaint;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.DisplayMetrics;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The extension utils class contains utility functions used by several
+ * extensions.
+ */
+public class ExtensionUtils {
+
+ /**
+ * Invalid id
+ */
+ public static final int INVALID_ID = -1;
+
+ /**
+ * Draw text on canvas. Shade if text too long to fit.
+ *
+ * @param canvas The canvas to draw in.
+ * @param text The text to draw.
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ * @param textPaint The paint to draw with.
+ * @param availableWidth The available width for the text
+ */
+ public static void drawText(Canvas canvas, String text, float x, float y, TextPaint textPaint,
+ int availableWidth) {
+ text = text.replaceAll("\\r?\\n", " ");
+ final TextPaint localTextPaint = new TextPaint(textPaint);
+ final float pixelsToShade = 1.5F * localTextPaint.getTextSize();
+ int characters = text.length();
+
+ if (localTextPaint.measureText(text) > availableWidth) {
+ Paint.Align align = localTextPaint.getTextAlign();
+ float shaderStopX;
+ characters = localTextPaint.breakText(text, true, availableWidth, null);
+ if (align == Paint.Align.LEFT) {
+ shaderStopX = x + availableWidth;
+ } else if (align == Paint.Align.CENTER) {
+ float[] measuredWidth = new float[1];
+ characters = localTextPaint.breakText(text, true, availableWidth, measuredWidth);
+ shaderStopX = x + (measuredWidth[0] / 2);
+ } else { // align == Paint.Align.RIGHT
+ shaderStopX = x;
+ }
+ // Hex 0x60000000 = first two bytes is alpha, gives semitransparent
+ localTextPaint.setShader(new LinearGradient(shaderStopX - pixelsToShade, 0,
+ shaderStopX, 0, localTextPaint.getColor(),
+ localTextPaint.getColor() + 0x60000000, Shader.TileMode.CLAMP));
+ }
+ canvas.drawText(text, 0, characters, x, y, localTextPaint);
+ }
+
+ /**
+ * Get URI string from resourceId.
+ *
+ * @param context The context.
+ * @param resourceId The resource id.
+ * @return The URI string.
+ */
+ public static String getUriString(final Context context, final int resourceId) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(context.getPackageName()).appendPath(Integer.toString(resourceId))
+ .toString();
+ }
+
+ /**
+ * Check in the database if there are any accessories connected.
+ *
+ * @param context The context
+ * @return True if at least one accessories is connected.
+ */
+ public static boolean areAnyAccessoriesConnected(Context context) {
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(Device.URI, null,
+ DeviceColumns.ACCESSORY_CONNECTED + " = 1", null, null);
+ if (cursor != null) {
+ return (cursor.getCount() > 0);
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query connected accessories", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the contact name from a URI.
+ *
+ * @param context The context.
+ * @param contactUri The contact URI.
+ *
+ * @return The contact name.
+ */
+ public static String getContactName(final Context context, Uri contactUri) {
+ String name = null;
+ if (contactUri != null) {
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(contactUri, new String[] {
+ ContactsContract.Contacts.DISPLAY_NAME
+ }, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ name = cursor.getString(cursor
+ .getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ }
+ }
+
+ return name;
+ }
+
+ /**
+ * Get the contact photo from a contact URI.
+ *
+ * @param context The context.
+ * @param contactUri The contact URI.
+ *
+ * @return The contact photo.
+ */
+ public static Bitmap getContactPhoto(final Context context, Uri contactUri) {
+ Bitmap bitmap = null;
+ if (contactUri != null) {
+ InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(
+ context.getContentResolver(), contactUri);
+ if (inputStream != null) {
+ bitmap = BitmapFactory.decodeStream(inputStream);
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Get bitmap from a URI.
+ *
+ * @param context The context.
+ * @param uriString The URI as a string.
+ *
+ * @return The bitmap.
+ */
+ public static Bitmap getBitmapFromUri(final Context context, String uriString) {
+ Bitmap bitmap = null;
+ if (uriString == null) {
+ return null;
+ }
+
+ Uri uri = Uri.parse(uriString);
+ if (uri != null) {
+ try {
+ bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
+ if (bitmap != null) {
+ // We use default density for all bitmaps to avoid scaling.
+ bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+ }
+ } catch (IOException e) {
+
+ }
+ }
+ return bitmap;
+ }
+
+ /**
+ * Get id of a registered extension
+ *
+ * @return Id, {@link #INVALID_ID} if extension is not registered
+ */
+ public static long getExtensionId(Context context) {
+ Cursor cursor = null;
+ long id = INVALID_ID;
+ String selection = Registration.ExtensionColumns.PACKAGE_NAME + " = ?";
+ String[] selectionArgs = new String[] {
+ context.getPackageName()
+ };
+ try {
+ cursor = context.getContentResolver().query(Registration.Extension.URI, null,
+ selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int idIndex = cursor.getColumnIndex(Registration.ExtensionColumns._ID);
+ id = cursor.getLong(idIndex);
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return id;
+ }
+
+ /**
+ * Get id of a registered extension
+ *
+ * @return Id, {@link #INVALID_ID} if extension is not registered
+ */
+ public static long getRegistrationId(Context context, String hostAppPackageName, long extensionId) {
+ Cursor cursor = null;
+ long id = INVALID_ID;
+ String selection = Registration.ApiRegistrationColumns.HOST_APPLICATION_PACKAGE
+ + " = ? AND " + Registration.ApiRegistrationColumns.EXTENSION_ID + " = ?";
+ String[] selectionArgs = new String[] {
+ hostAppPackageName, Long.toString(extensionId)
+ };
+ try {
+ cursor = context.getContentResolver().query(Registration.ApiRegistration.URI, null,
+ selection, selectionArgs, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int idIndex = cursor.getColumnIndex(Registration.ApiRegistrationColumns._ID);
+ id = cursor.getLong(idIndex);
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query extension", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return id;
+ }
+
+ /**
+ * Get the value of the intent extra parameter
+ * {@link Registration.Intents#EXTRA_ACCESSORY_SUPPORTS_HISTORY} from the
+ * intent that started the configuration activity.
+ *
+ * @param intent The intent that started the configuration activity, see
+ * {@link Registration.ExtensionColumns#CONFIGURATION_ACTIVITY}
+ * @return Value of
+ * {@link Registration.Intents#EXTRA_ACCESSORY_SUPPORTS_HISTORY},
+ * true if not contained in the intent extras.
+ */
+ public static boolean supportsHistory(Intent intent) {
+ boolean supportsHistory = true;
+ if (intent == null) {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsHistory: intent == null");
+ }
+ return supportsHistory;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsHistory: extras == null");
+ }
+ return supportsHistory;
+ }
+ if (extras.containsKey(Registration.Intents.EXTRA_ACCESSORY_SUPPORTS_HISTORY)) {
+ supportsHistory = extras
+ .getBoolean(Registration.Intents.EXTRA_ACCESSORY_SUPPORTS_HISTORY);
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsHistory: EXTRA_ACCESSORY_SUPPORTS_HISTORY not present");
+ }
+ }
+ return supportsHistory;
+ }
+
+ /**
+ * Get the value of the intent extra parameter
+ * {@link Registration.Intents#EXTRA_ACCESSORY_SUPPORTS_ACTIONS} from the
+ * intent that started the configuration activity.
+ *
+ * @param intent The intent that started the configuration activity, see
+ * {@link Registration.ExtensionColumns#CONFIGURATION_ACTIVITY}
+ * @return Value of
+ * {@link Registration.Intents#EXTRA_ACCESSORY_SUPPORTS_ACTIONS},
+ * true if not contained in the intent extras.
+ */
+ public static boolean supportsActions(Intent intent) {
+ boolean supportsActions = true;
+ if (intent == null) {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsActions: intent == null");
+ }
+ return supportsActions;
+ }
+ Bundle extras = intent.getExtras();
+ if (extras == null) {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsActions: extras == null");
+ }
+ return supportsActions;
+ }
+ if (extras.containsKey(Registration.Intents.EXTRA_ACCESSORY_SUPPORTS_ACTIONS)) {
+ supportsActions = extras
+ .getBoolean(Registration.Intents.EXTRA_ACCESSORY_SUPPORTS_ACTIONS);
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.e("ExtensionUtils.supportsActions: EXTRA_ACCESSORY_SUPPORTS_ACTIONS not present");
+ }
+ }
+ return supportsActions;
+ }
+
+ /**
+ * Get formatted time.
+ *
+ * @param publishedTime The published time in millis.
+ *
+ * @return The formatted time.
+ */
+ static public String getFormattedTime(long publishedTime) {
+ // This is copied from RecentCallsListActivity.java
+
+ long now = System.currentTimeMillis();
+
+ // Set the date/time field by mixing relative and absolute times.
+ int flags = DateUtils.FORMAT_ABBREV_ALL;
+
+ if (!DateUtils.isToday(publishedTime)) {
+ // DateUtils.getRelativeTimeSpanString doesn't consider the nature
+ // days comparing with DateUtils.getRelativeDayString. Override the
+ // real date to implement the requirement.
+
+ Time time = new Time();
+ time.set(now);
+ long gmtOff = time.gmtoff;
+ int days = Time.getJulianDay(publishedTime, gmtOff) - Time.getJulianDay(now, gmtOff);
+
+ // Set the delta from now to get the correct display
+ publishedTime = now + days * DateUtils.DAY_IN_MILLIS;
+ } else if (publishedTime > now && (publishedTime - now) < DateUtils.HOUR_IN_MILLIS) {
+ // Avoid e.g. "1 minute left" when publish time is "07:00" and
+ // current time is "06:58"
+ publishedTime += DateUtils.MINUTE_IN_MILLIS;
+ }
+
+ return (DateUtils.getRelativeTimeSpanString(publishedTime, now, DateUtils.MINUTE_IN_MILLIS,
+ flags)).toString();
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/SmartWatchConst.java b/src/com/sonyericsson/extras/liveware/extension/util/SmartWatchConst.java
new file mode 100644
index 0000000..02acdc2
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/SmartWatchConst.java
@@ -0,0 +1,45 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import android.graphics.Rect;
+
+public class SmartWatchConst {
+
+ /**
+ * For a widget only taps in the 80 by 80 pixels in the center of the screen
+ * shall be considered as taps. The is to avoid interpreting failed swipe
+ * motions as tap events.
+ */
+ public static final Rect ACTIVE_WIDGET_TOUCH_AREA = new Rect(24, 6, 24 + 80, 6 + 80);
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/SmartWirelessHeadsetProUtil.java b/src/com/sonyericsson/extras/liveware/extension/util/SmartWirelessHeadsetProUtil.java
new file mode 100644
index 0000000..e0c906e
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/SmartWirelessHeadsetProUtil.java
@@ -0,0 +1,153 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.util.DisplayMetrics;
+
+import com.dattasmoon.pebble.plugin.R;
+
+/**
+ * This class contains Smart Wireless Headset pro specific utility functions and
+ * constants
+ */
+public class SmartWirelessHeadsetProUtil {
+
+ public static final int DISPLAY_WIDTH = 128;
+
+ public static final int DISPLAY_HEIGHT = 36;
+
+ public static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+ public static final int CONFIRM_PADDING = 1;
+
+ public static final int CONFIRM_TEXT_X = 1;
+
+ public static final int CONFIRM_TEXT_Y = 22;
+
+ /**
+ * Create text paint for Smart Wireless Headset pro for current locale.
+ *
+ * @param context
+ * The context.
+ * @return The text paint.
+ */
+ public static TextPaint createTextPaint(final Context context) {
+ TextPaint textPaint = new TextPaint();
+
+ textPaint.setTextSize(context.getResources().getDimensionPixelSize(
+ R.dimen.headset_pro_text_size));
+ textPaint.setTypeface(Typeface.DEFAULT_BOLD);
+ textPaint.setColor(Color.WHITE);
+ return textPaint;
+ }
+
+ /**
+ * Generate bitmap with ok and cancel icons and a text.
+ *
+ * @param context
+ * The context.
+ * @param text
+ * The text.
+ * @param okInFocus
+ * true if ok icon should be focused else false.
+ * @param hideCancel
+ * true if cancel icon should be hidden.
+ *
+ * @return The text paint.
+ */
+ public static Bitmap getConfirmBitmap(final Context context,
+ final String text, final boolean okInFocus, final boolean hideCancel) {
+ Bitmap bitmap = Bitmap.createBitmap(DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ BITMAP_CONFIG);
+ // Set the density to default to avoid scaling.
+ bitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+
+ Canvas canvas = new Canvas(bitmap);
+ // Black background
+ canvas.drawColor(Color.BLACK);
+
+ // draw text
+ TextPaint textPaint = SmartWirelessHeadsetProUtil
+ .createTextPaint(context);
+ canvas.drawText(text, 0, text.length(), CONFIRM_TEXT_X, CONFIRM_TEXT_Y,
+ textPaint);
+
+ BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
+ // We use default throughout the extension to avoid any automatic
+ // scaling.
+ // Keep in mind that we are not showing the images on the phone, but on
+ // the accessory.
+ bitmapOptions.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ bitmapOptions.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+
+ Bitmap focusBitmap = BitmapFactory.decodeResource(
+ context.getResources(), R.drawable.headset_pro_focus_xs_icn,
+ bitmapOptions);
+ Bitmap okBitmap = BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.headset_pro_ok_icn, bitmapOptions);
+ Bitmap cancelBitmap = BitmapFactory.decodeResource(
+ context.getResources(), R.drawable.headset_pro_cancel_icn,
+ bitmapOptions);
+
+ // draw focus mark
+ if (okInFocus) {
+ canvas.drawBitmap(focusBitmap,
+ DISPLAY_WIDTH - focusBitmap.getWidth(), 0, null);
+ } else if (!hideCancel) {
+ canvas.drawBitmap(focusBitmap,
+ DISPLAY_WIDTH - focusBitmap.getWidth(), DISPLAY_HEIGHT / 2,
+ null);
+ }
+
+ // draw ok
+ canvas.drawBitmap(okBitmap, DISPLAY_WIDTH - okBitmap.getWidth()
+ - CONFIRM_PADDING, CONFIRM_PADDING, null);
+
+ // draw cancel
+ if (!hideCancel) {
+ canvas.drawBitmap(
+ cancelBitmap,
+ DISPLAY_WIDTH - cancelBitmap.getWidth() - CONFIRM_PADDING,
+ DISPLAY_HEIGHT - cancelBitmap.getHeight() - CONFIRM_PADDING,
+ null);
+ }
+ return bitmap;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlExtension.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlExtension.java
new file mode 100644
index 0000000..c7e2085
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlExtension.java
@@ -0,0 +1,742 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Device;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostApp;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostAppColumns;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * The control extension handles a control on an accessory.
+ */
+public abstract class ControlExtension {
+
+ private static final int STATE_CREATED = 0;
+
+ private static final int STATE_STARTED = 1;
+
+ private static final int STATE_FOREGROUND = 2;
+
+ private int mState = STATE_CREATED;
+
+ protected final Context mContext;
+
+ protected final String mHostAppPackageName;
+
+ protected final BitmapFactory.Options mBitmapOptions;
+
+ /**
+ * Create control extension.
+ *
+ * @param context The context.
+ * @param hostAppPackageName Package name of host application.
+ */
+ public ControlExtension(final Context context, final String hostAppPackageName) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ mContext = context;
+ mHostAppPackageName = hostAppPackageName;
+
+ // Set some default bitmap factory options that we frequently will use.
+ mBitmapOptions = new BitmapFactory.Options();
+ // We use default throughout the extension to avoid any automatic
+ // scaling.
+ // Keep in mind that we are not showing the images on the phone, but on
+ // the accessory.
+ mBitmapOptions.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ mBitmapOptions.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+ }
+
+ /**
+ * Start control.
+ */
+ public final void start() {
+ mState = STATE_STARTED;
+ onStart();
+ }
+
+ /**
+ * Resume control.
+ */
+ public final void resume() {
+ mState = STATE_FOREGROUND;
+ onResume();
+ }
+
+ /**
+ * Pause control.
+ */
+ public final void pause() {
+ mState = STATE_STARTED;
+ onPause();
+ }
+
+ /**
+ * Stop control.
+ */
+ public final void stop() {
+ // If in foreground then pause it.
+ if (mState == STATE_FOREGROUND) {
+ pause();
+ }
+
+ mState = STATE_CREATED;
+ onStop();
+ }
+
+ /**
+ * Destroy control.
+ */
+ public final void destroy() {
+ // If in foreground then pause it.
+ if (mState == STATE_FOREGROUND) {
+ pause();
+ }
+ // If started then stop it.
+ if (mState == STATE_STARTED) {
+ stop();
+ }
+
+ // No state for destroyed.
+ onDestroy();
+ }
+
+ /**
+ * Take action based on request code
+ *
+ * @see ControlReceiver#doActionOnAllControls(int)
+ * @param requestCode Code used to distinguish between different actions.
+ * @param bundle Optional bundle with additional information.
+ */
+ public void onDoAction(int requestCode, Bundle bundle) {
+
+ }
+
+ /**
+ * Called to notify a control extension that it is no longer used and is
+ * being removed. The control extension should clean up any resources it
+ * holds (threads, registered receivers, etc) at this point.
+ */
+ public void onDestroy() {
+
+ }
+
+ /**
+ * Called when the control extension is started by the host application.
+ */
+ public void onStart() {
+
+ }
+
+ /**
+ * Called when the control extension is stopped by the host application.
+ */
+ public void onStop() {
+
+ }
+
+ /**
+ * Called when the control extension is paused by the host application.
+ */
+ public void onPause() {
+ }
+
+ /**
+ * Called when the control extension is resumed by the host application. The
+ * extension is expected to send a new image each time it is resumed.
+ */
+ public void onResume() {
+ }
+
+ /**
+ * Called when host application reports an error.
+ *
+ * @param code The reported error code.
+ * {@link Control.Intents#EXTRA_ERROR_CODE}
+ */
+ public void onError(final int code) {
+
+ }
+
+ /**
+ * Called when a key event has occurred.
+ *
+ * @param action The key action, one of
+ *
+ * - {@link Control.Intents#ACTION_LONGPRESS}
+ * - {@link Control.Intents#ACTION_PRESS}
+ * - {@link Control.Intents#ACTION_RELEASE}
+ *
+ * @param keyCode The key code.
+ * @param timeStamp The time when the event occurred.
+ */
+ public void onKey(final int action, final int keyCode, final long timeStamp) {
+
+ }
+
+ /**
+ * Called when a touch event has occurred.
+ *
+ * @param event The touch event.
+ */
+ public void onTouch(final ControlTouchEvent event) {
+
+ }
+
+ /**
+ * Called when an object click event has occurred.
+ *
+ * @param event The object click event.
+ */
+ public void onObjectClick(final ControlObjectClickEvent event) {
+
+ }
+
+ /**
+ * Called when a swipe event has occurred
+ *
+ * @param direction The swipe direction, one of
+ *
+ * - {@link Control.Intents#DIRECTION_DOWN}
+ * - {@link Control.Intents#DIRECTION_LEFT}
+ * - {@link Control.Intents#DIRECTION_RIGHT}
+ * - {@link Control.Intents#DIRECTION_UP}
+ *
+ */
+ public void onSwipe(int direction) {
+
+ }
+
+ /**
+ * Called when the host application requests content for a specific list
+ * item.
+ *
+ * @param layoutReference The referenced list view
+ * @param listItemPosition The referenced list item position
+ */
+ public void onRequestListItem(final int layoutReference, final int listItemPosition) {
+
+ }
+
+ /**
+ * Called when an item in a list has been clicked.
+ *
+ * @param listItem The list item that was clicked
+ * @param clickType The type of click (long, short)
+ * @param itemLayoutReference The object within the list item that was
+ * clicked
+ */
+ public void onListItemClick(final ControlListItem listItem, final int clickType,
+ final int itemLayoutReference) {
+
+ }
+
+ /**
+ * Called when an item in a list has been clicked.
+ *
+ * @param layoutReference The referenced list view
+ */
+ public void onListRefreshRequest(final int layoutReference) {
+
+ }
+
+ /**
+ * Called when an item in a list has been selected.
+ *
+ * @param listItem The list item that was selected
+ */
+ public void onListItemSelected(final ControlListItem listItem) {
+
+ }
+
+ /**
+ * Called when a menu item has been selected.
+ *
+ * @param menuItem The menu item that was selected
+ */
+ public void onMenuItemSelected(final int menuItem) {
+
+ }
+
+ /**
+ * Called when the accessory screen's state leaves or enters Active Low
+ * Power mode
+ *
+ * @param lowPowerModeOn true when the low power mode is started, false when
+ * the accessory screen leaves Active Low power mode
+ */
+ public void onActiveLowPowerModeChange(final boolean lowPowerModeOn) {
+
+ }
+
+ /**
+ * Send request to start to host application.
+ */
+ protected void startRequest() {
+ if (Dbg.DEBUG) {
+ Dbg.d("Sending start request");
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_START_REQUEST_INTENT);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Send request to stop to host application.
+ */
+ protected void stopRequest() {
+ if (Dbg.DEBUG) {
+ Dbg.d("Sending stop request");
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_STOP_REQUEST_INTENT);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Show an image on the accessory.
+ *
+ * @param resourceId The image resource id.
+ */
+ protected void showImage(final int resourceId) {
+ if (Dbg.DEBUG) {
+ Dbg.d("showImage: " + resourceId);
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(Control.Intents.CONTROL_DISPLAY_DATA_INTENT);
+
+ Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), resourceId,
+ mBitmapOptions);
+ ByteArrayOutputStream os = new ByteArrayOutputStream(256);
+ bitmap.compress(CompressFormat.PNG, 100, os);
+ byte[] buffer = os.toByteArray();
+ intent.putExtra(Control.Intents.EXTRA_DATA, buffer);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Show a layout on the accessory.
+ *
+ * @param layoutId The layout resource id.
+ * @param layoutData The layout data.
+ */
+ protected void showLayout(final int layoutId, final Bundle[] layoutData) {
+ if (Dbg.DEBUG) {
+ Dbg.d("showLayout");
+ }
+
+ Intent intent = new Intent(Control.Intents.CONTROL_PROCESS_LAYOUT_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_DATA_XML_LAYOUT, layoutId);
+ if (layoutData != null && layoutData.length > 0) {
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_DATA, layoutData);
+ }
+
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Update an image in a specific layout, on the accessory.
+ *
+ * @param layoutReference The referenced resource within the current layout.
+ * @param resourceId The image resource id.
+ */
+ protected void sendImage(final int layoutReference, final int resourceId) {
+ if (Dbg.DEBUG) {
+ Dbg.d("sendImage");
+ }
+
+ Intent intent = new Intent(Control.Intents.CONTROL_SEND_IMAGE_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_DATA_URI,
+ ExtensionUtils.getUriString(mContext, resourceId));
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Update an image in a specific layout, on the accessory.
+ *
+ * @param layoutReference The referenced resource within the current layout.
+ * @param bitmap The bitmap to show.
+ */
+ protected void sendImage(final int layoutReference, final Bitmap bitmap) {
+ if (Dbg.DEBUG) {
+ Dbg.d("sendImage");
+ }
+
+ Intent intent = new Intent(Control.Intents.CONTROL_SEND_IMAGE_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ ByteArrayOutputStream os = new ByteArrayOutputStream(256);
+ bitmap.compress(CompressFormat.PNG, 100, os);
+ byte[] buffer = os.toByteArray();
+ intent.putExtra(Control.Intents.EXTRA_DATA, buffer);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Update text in a specific layout, on the accessory. TODO: This should be
+ * moved to ControlExtension
+ *
+ * @param layoutReference The referenced resource within the current layout.
+ * @param resourceId The image resource id.
+ */
+ protected void sendText(final int layoutReference, final String text) {
+ if (Dbg.DEBUG) {
+ Dbg.d("sendText: " + text);
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_SEND_TEXT_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_TEXT, text);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Show bitmap on accessory.
+ *
+ * @param bitmap The bitmap to show.
+ */
+ protected void showBitmap(final Bitmap bitmap) {
+ if (Dbg.DEBUG) {
+ Dbg.d("showBitmap");
+ }
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
+ bitmap.compress(CompressFormat.PNG, 100, outputStream);
+
+ Intent intent = new Intent(Control.Intents.CONTROL_DISPLAY_DATA_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_DATA, outputStream.toByteArray());
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Show bitmap on accessory. Used when only updating part of the screen.
+ *
+ * @param bitmap The bitmap to show.
+ * @param x The x position.
+ * @param y The y position.
+ */
+ protected void showBitmap(final Bitmap bitmap, final int x, final int y) {
+ if (Dbg.DEBUG) {
+ Dbg.v("showBitmap x: " + x + " y: " + y);
+ }
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
+ bitmap.compress(CompressFormat.PNG, 100, outputStream);
+
+ Intent intent = new Intent(Control.Intents.CONTROL_DISPLAY_DATA_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_X_OFFSET, x);
+ intent.putExtra(Control.Intents.EXTRA_Y_OFFSET, y);
+ intent.putExtra(Control.Intents.EXTRA_DATA, outputStream.toByteArray());
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Set the accessory screens state.
+ *
+ * @see Control.Intents#SCREEN_STATE_AUTO
+ * @see Control.Intents#SCREEN_STATE_DIM
+ * @see Control.Intents#SCREEN_STATE_OFF
+ * @see Control.Intents#SCREEN_STATE_ON
+ * @param state The screen state.
+ */
+ protected void setScreenState(final int state) {
+ if (Dbg.DEBUG) {
+ Dbg.d("setScreenState: " + state);
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_SET_SCREEN_STATE_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_SCREEN_STATE, state);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Start repeating vibrator
+ *
+ * @param onDuration On duration in milliseconds.
+ * @param offDuration Off duration in milliseconds.
+ * @param repeats The number of repeats of the on/off pattern. Use
+ * {@link Control.Intents#REPEAT_UNTIL_STOP_INTENT} to repeat
+ * until explicitly stopped.
+ */
+ protected void startVibrator(int onDuration, int offDuration, int repeats) {
+ if (Dbg.DEBUG) {
+ Dbg.v("startVibrator: onDuration: " + onDuration + ", offDuration: " + offDuration
+ + ", repeats: " + repeats);
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_VIBRATE_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_ON_DURATION, onDuration);
+ intent.putExtra(Control.Intents.EXTRA_OFF_DURATION, offDuration);
+ intent.putExtra(Control.Intents.EXTRA_REPEATS, repeats);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Stop vibrator.
+ */
+ protected void stopVibrator() {
+ if (Dbg.DEBUG) {
+ Dbg.v("Vibrator stop");
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_STOP_VIBRATE_INTENT);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Start a LED pattern.
+ *
+ * @param id Id of the LED to be controlled.
+ * @param color Color you want the LED to blink with.
+ * @param onDuration On duration in milliseconds.
+ * @param offDuration Off duration in milliseconds.
+ * @param repeats The number of repeats of the on/off pattern. Use
+ * {@link Control.Intents#REPEAT_UNTIL_STOP_INTENT} to repeat
+ * until explicitly stopped.
+ */
+ protected void startLedPattern(int id, int color, int onDuration, int offDuration, int repeats) {
+ if (Dbg.DEBUG) {
+ Dbg.v("startLedPattern: id: " + id + ", color: " + color + "onDuration: " + onDuration
+ + ", offDuration: " + offDuration + ", repeats: " + repeats);
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_LED_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LED_ID, id);
+ intent.putExtra(Control.Intents.EXTRA_LED_COLOR, color);
+ intent.putExtra(Control.Intents.EXTRA_ON_DURATION, onDuration);
+ intent.putExtra(Control.Intents.EXTRA_OFF_DURATION, offDuration);
+ intent.putExtra(Control.Intents.EXTRA_REPEATS, repeats);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Turn led off
+ *
+ * @param id Id of the LED to be controlled.
+ */
+ protected void stopLedPattern(int id) {
+ if (Dbg.DEBUG) {
+ Dbg.v("stopLedPattern: id: " + id);
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_LED_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LED_ID, id);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Clear accessory diplay.
+ */
+ protected void clearDisplay() {
+ if (Dbg.DEBUG) {
+ Dbg.v("Clear display");
+ }
+ Intent intent = new Intent(Control.Intents.CONTROL_CLEAR_DISPLAY_INTENT);
+ sendToHostApp(intent);
+ }
+
+ protected void sendListCount(int layoutReference, int listCount) {
+ Intent intent = new Intent(Control.Intents.CONTROL_LIST_COUNT_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_LIST_COUNT, listCount);
+ sendToHostApp(intent);
+ }
+
+ protected void sendListCountWithContent(int layoutReference, int listCount, Bundle[] bundles) {
+ Intent intent = new Intent(Control.Intents.CONTROL_LIST_COUNT_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_LIST_COUNT, listCount);
+ intent.putExtra(Control.Intents.EXTRA_LIST_CONTENT, bundles);
+ sendToHostApp(intent);
+ }
+
+ protected void sendListItem(ControlListItem item) {
+ Intent intent = new Intent(Control.Intents.CONTROL_LIST_ITEM_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, item.layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_DATA_XML_LAYOUT, item.dataXmlLayout);
+ if (item.listItemId != -1) {
+ intent.putExtra(Control.Intents.EXTRA_LIST_ITEM_ID, item.listItemId);
+ }
+ if (item.listItemPosition != -1) {
+ intent.putExtra(Control.Intents.EXTRA_LIST_ITEM_POSITION, item.listItemPosition);
+ }
+ if (item.layoutData != null && item.layoutData.length > 0) {
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_DATA, item.layoutData);
+ }
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Sends a request to host app to move a list to a specified position
+ *
+ * @param layoutReference The referenced list view
+ * @param listItemPosition The referenced list item position
+ */
+ protected void sendListPosition(int layoutReference, int position) {
+ Intent intent = new Intent(Control.Intents.CONTROL_LIST_MOVE_INTENT);
+ intent.putExtra(Control.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Control.Intents.EXTRA_LIST_ITEM_POSITION, position);
+ sendToHostApp(intent);
+ }
+
+ protected void showMenu(Bundle[] menuItems) {
+ Intent intent = new Intent(Control.Intents.CONTROL_MENU_SHOW);
+ intent.putExtra(Control.Intents.EXTRA_MENU_ITEMS, menuItems);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Send intent to host application. Adds host application package name and
+ * our package name.
+ *
+ * @param intent The intent to send.
+ */
+ protected void sendToHostApp(final Intent intent) {
+ intent.putExtra(Control.Intents.EXTRA_AEA_PACKAGE_NAME, mContext.getPackageName());
+ intent.setPackage(mHostAppPackageName);
+ mContext.sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);
+ }
+
+ /**
+ * Get the host application id for this control.
+ *
+ * @return The host application id.
+ */
+ protected long getHostAppId() {
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(HostApp.URI,
+ new String[] {
+ HostAppColumns._ID
+ }, HostAppColumns.PACKAGE_NAME + " = ?",
+ new String[] {
+ mHostAppPackageName
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ return cursor.getLong(cursor.getColumnIndexOrThrow(HostAppColumns._ID));
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Check if this host application has a vibrator.
+ *
+ * @return True if vibrator exists.
+ */
+ protected boolean hasVibrator() {
+ long hostAppId = getHostAppId();
+
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(
+ Device.URI,
+ new String[] {
+ DeviceColumns.VIBRATOR
+ },
+ DeviceColumns.HOST_APPLICATION_ID + " = " + hostAppId + " AND "
+ + DeviceColumns.VIBRATOR + " = 1", null, null);
+ if (cursor != null) {
+ return (cursor.getCount() > 0);
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query vibrator", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query vibrator", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to query vibrator", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return false;
+ }
+
+ protected ControlViewGroup parseLayout(View v) {
+ ControlViewGroup controlViewGroup = new ControlViewGroup();
+ controlViewGroup.addView(new ControlView(v.getId(), v.isClickable(), v.isLongClickable()));
+ if (v instanceof ViewGroup) {
+ parseLayoutTraverse((ViewGroup) v, controlViewGroup);
+ }
+ return controlViewGroup;
+ }
+
+ private void parseLayoutTraverse(ViewGroup v, ControlViewGroup controlViewGroup) {
+ for (int i = 0; i < v.getChildCount(); i++) {
+ View current = v.getChildAt(i);
+ controlViewGroup.addView(new ControlView(current.getId(), current.isClickable(),
+ current
+ .isLongClickable()));
+ if (current instanceof ViewGroup) {
+ parseLayoutTraverse((ViewGroup) current, (ControlViewGroup) controlViewGroup);
+ }
+ }
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlListItem.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlListItem.java
new file mode 100644
index 0000000..023ff48
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlListItem.java
@@ -0,0 +1,14 @@
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+import android.os.Bundle;
+
+public class ControlListItem {
+
+ public int layoutReference;
+ public int dataXmlLayout;
+ public int listItemId = -1;
+ public int listItemPosition = -1;
+ public Bundle[] layoutData;
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlObjectClickEvent.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlObjectClickEvent.java
new file mode 100644
index 0000000..99830b5
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlObjectClickEvent.java
@@ -0,0 +1,88 @@
+/*
+Copyright (c) 2013, Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+
+/**
+ * The control object click event class holds information about an object click
+ * event.
+ */
+public class ControlObjectClickEvent {
+ private final int mClickType;
+ private final long mTimeStamp;
+ private final int mLayoutReference;
+
+ /**
+ * Create click event.
+ *
+ * @see Control.Intents#CLICK_TYPE_SHORT
+ * @see Control.Intents#CLICK_TYPE_LONG
+ * @param clickType The click type.
+ * @param timeStamp The time when the event occurred.
+ * @param layoutReference Reference to view in the layout
+ */
+ public ControlObjectClickEvent(final int clickType, final long timeStamp,
+ final int layoutReference) {
+ mClickType = clickType;
+ mTimeStamp = timeStamp;
+ mLayoutReference = layoutReference;
+ }
+
+ /**
+ * Get the click type.
+ *
+ * @return The click type.
+ */
+ public int getClickType() {
+ return mClickType;
+ }
+
+ /**
+ * Get the touch event time stamp.
+ *
+ * @return The time stamp.
+ */
+ public long getTimeStamp() {
+ return mTimeStamp;
+ }
+
+ /**
+ * Get the layout reference.
+ *
+ * @return Reference to view in the layout.
+ */
+ public int getLayoutReference() {
+ return mLayoutReference;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlTouchEvent.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlTouchEvent.java
new file mode 100644
index 0000000..bae6111
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlTouchEvent.java
@@ -0,0 +1,100 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+
+/**
+ * The control touch event class holds information about
+ * a touch event.
+ */
+public class ControlTouchEvent {
+ private final int mAction;
+ private final long mTimeStamp;
+ private final int mX;
+ private final int mY;
+
+ /**
+ * Create touch event.
+ *
+ * @see Control.Intents#ACTION_PRESS
+ * @see Control.Intents#ACTION_LONGPRESS
+ * @see Control.Intents#ACTION_RELEASE
+ *
+ * @param action Touch action.
+ * @param timeStamp The time when the event occurred.
+ * @param x The x position.
+ * @param y The y position.
+ */
+ public ControlTouchEvent(final int action, final long timeStamp, final int x, final int y) {
+ mAction = action;
+ mTimeStamp = timeStamp;
+ mX = x;
+ mY = y;
+ }
+
+ /**
+ * Get the touch event action.
+ *
+ * @return The action.
+ */
+ public int getAction() {
+ return mAction;
+ }
+
+ /**
+ * Get the touch event time stamp.
+ *
+ * @return The time stamp.
+ */
+ public long getTimeStamp() {
+ return mTimeStamp;
+ }
+
+ /**
+ * Get the touch event x position.
+ *
+ * @return The x position.
+ */
+ public int getX() {
+ return mX;
+ }
+
+ /**
+ * Get the touch event y position.
+ *
+ * @return The y position.
+ */
+ public int getY() {
+ return mY;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlView.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlView.java
new file mode 100644
index 0000000..4dc1695
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlView.java
@@ -0,0 +1,99 @@
+/*
+Copyright (c) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+
+public class ControlView {
+
+ private int id;
+ private boolean isClickable;
+ private boolean isLongClickable;
+
+ static class ListenerInfo {
+ public OnClickListener mOnClickListener;
+ public OnLongClickListener mOnLongClickListener;
+ }
+
+ ListenerInfo mListenerInfo;
+
+ public ControlView(int id, boolean isClickable, boolean isLongClickable) {
+ this.id = id;
+ this.isClickable = isClickable;
+ this.isLongClickable = isLongClickable;
+ }
+
+ public interface OnClickListener {
+ void onClick();
+
+ }
+
+ public interface OnLongClickListener {
+ void onLongClick();
+ }
+
+ public void setOnClickListener(OnClickListener l) {
+ if (isClickable) {
+ getListenerInfo().mOnClickListener = l;
+ }
+ }
+
+ public void setOnLongClickListener(OnLongClickListener l) {
+ if (isLongClickable) {
+ getListenerInfo().mOnLongClickListener = l;
+ }
+ }
+
+ public void onClick() {
+ if (isClickable && getListenerInfo().mOnClickListener != null) {
+ getListenerInfo().mOnClickListener.onClick();
+ }
+ }
+
+ public void onLongClick() {
+ if (isLongClickable && getListenerInfo().mOnLongClickListener != null) {
+ getListenerInfo().mOnLongClickListener.onLongClick();
+ }
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ private ListenerInfo getListenerInfo() {
+ if (mListenerInfo != null) {
+ return mListenerInfo;
+ }
+ mListenerInfo = new ListenerInfo();
+ return mListenerInfo;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/control/ControlViewGroup.java b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlViewGroup.java
new file mode 100644
index 0000000..bc22620
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/control/ControlViewGroup.java
@@ -0,0 +1,73 @@
+/*
+Copyright (c) 2013 Sony Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.control;
+
+import android.util.SparseArray;
+import android.view.View;
+
+public class ControlViewGroup {
+
+ private SparseArray mViews;
+
+ public void addView(ControlView view) {
+ int id = view.getId();
+ if (id != View.NO_ID) {
+ getViews().put(id, view);
+ }
+ }
+
+ public void onClick(int id) {
+ ControlView view = getViews().get(id);
+ if (view != null) {
+ view.onClick();
+ }
+ }
+
+ public void onLongClick(int id) {
+ ControlView view = getViews().get(id);
+ if (view != null) {
+ view.onLongClick();
+ }
+ }
+
+ public ControlView findViewById(int id) {
+ return getViews().get(id);
+ }
+
+ private SparseArray getViews() {
+ if (mViews == null) {
+ mViews = new SparseArray();
+ }
+ return mViews;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/notification/DelayedContentObserver.java b/src/com/sonyericsson/extras/liveware/extension/util/notification/DelayedContentObserver.java
new file mode 100644
index 0000000..252c396
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/notification/DelayedContentObserver.java
@@ -0,0 +1,86 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.notification;
+
+import android.database.ContentObserver;
+import android.os.Handler;
+
+/**
+ * The delayed content observer is used to handle a content observer change
+ * after a delay. If more content observer changes are triggered during the
+ * delay the original change is deleted and a new delay is set.
+ */
+public abstract class DelayedContentObserver extends ContentObserver {
+
+ public static final int EVENT_READ_DELAY = 1000;
+
+ public static final int CONTACTS_UPDATE_DELAY = 3000;
+
+ private final Handler mHandler;
+
+ private final int mDelay;
+
+ private final Runnable mDelayedRunnable = new Runnable() {
+ public void run() {
+ onChangeDelayed();
+ }
+ };
+
+ /**
+ * Create delayed content observer.
+ *
+ * @param handler The handler.
+ * @param delay The delay in ms.
+ */
+ public DelayedContentObserver(final Handler handler, final int delay) {
+ super(handler);
+ if (handler == null) {
+ throw new IllegalArgumentException("handler is null");
+ }
+
+ mHandler = handler;
+ mDelay = delay;
+ }
+
+ /**
+ * onChangeDelayed is called when the delay has passed and no new changes
+ * has been triggered during the delay.
+ */
+ public abstract void onChangeDelayed();
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.removeCallbacks(mDelayedRunnable);
+ mHandler.postDelayed(mDelayedRunnable, mDelay);
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/notification/NotificationUtil.java b/src/com/sonyericsson/extras/liveware/extension/util/notification/NotificationUtil.java
new file mode 100644
index 0000000..de0ac71
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/notification/NotificationUtil.java
@@ -0,0 +1,696 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.notification;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.registration.DeviceInfoHelper;
+
+import java.util.ArrayList;
+
+public class NotificationUtil {
+
+ /**
+ * Invalid id
+ */
+ public static final int INVALID_ID = -1;
+
+ /**
+ * Event Id to use in projection, selection and sortOrder when quering
+ * {@link Notification.SourceEvent#URI}
+ *
+ * @see #queryEvents(Context, String[], String, String[], String)
+ */
+ public static final String EVENT_ID = Notification.Event.TABLE_NAME + "." + BaseColumns._ID;
+
+ /** This class can not be instantiated */
+ private NotificationUtil() {
+ }
+
+ /**
+ * Get source id associated with extension specific id of the source.
+ *
+ * @param context Context with permissions to access Notification db
+ * @param extensionSpecificId Extension specific identifier of the source.
+ * @return Source id, INVALID_ID if not found
+ */
+ public static long getSourceId(Context context, String extensionSpecificId) {
+ long sourceId = INVALID_ID;
+ Cursor cursor = null;
+
+ String whereClause = null;
+ if (extensionSpecificId != null) {
+ whereClause = Notification.SourceColumns.EXTENSION_SPECIFIC_ID + " = '"
+ + extensionSpecificId + "'";
+ }
+
+ try {
+ cursor = querySources(context, new String[] {
+ Notification.SourceColumns._ID,
+ Notification.SourceColumns.EXTENSION_SPECIFIC_ID
+ }, whereClause, null, null);
+ if (cursor == null) {
+ return INVALID_ID;
+ }
+
+ if (cursor.moveToFirst()) {
+ sourceId = cursor.getLong(cursor.getColumnIndex(Notification.SourceColumns._ID));
+ }
+ } catch (SQLException exception) {
+ return INVALID_ID;
+ } catch (SecurityException exception) {
+ return INVALID_ID;
+ } catch (IllegalArgumentException exception) {
+ return INVALID_ID;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return sourceId;
+ }
+
+ /**
+ * Get extension specific id.
+ *
+ * @param context Context with permissions to access Notification db
+ * @param sourceId The source id.
+ * @return Extension specific id, null if not found
+ */
+ public static String getExtensionSpecificId(Context context, long sourceId) {
+ String extensionSpecificId = null;
+ Cursor cursor = null;
+
+ try {
+ cursor = querySources(context, new String[] {
+ Notification.SourceColumns._ID,
+ Notification.SourceColumns.EXTENSION_SPECIFIC_ID
+ }, Notification.SourceColumns._ID + " = " + sourceId, null, null);
+ if (cursor == null) {
+ return null;
+ }
+
+ if (cursor.moveToFirst()) {
+ int index = cursor.getColumnIndex(Notification.SourceColumns.EXTENSION_SPECIFIC_ID);
+ extensionSpecificId = cursor.getString(index);
+ }
+ } catch (SQLException exception) {
+ return null;
+ } catch (SecurityException exception) {
+ return null;
+ } catch (IllegalArgumentException exception) {
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return extensionSpecificId;
+ }
+
+ /**
+ * Delete all events associated with a extension specific id
+ *
+ * @param context The context.
+ * @param extensionSpecificId The extension specific id
+ * @return The number of events that was deleted or {@link #INVALID_ID} on
+ * failure
+ */
+ public static int deleteAllEvents(Context context, String extensionSpecificId) {
+ int result = 0;
+
+ long sourceId = getSourceId(context, extensionSpecificId);
+ String where = Notification.EventColumns.SOURCE_ID + " = " + sourceId;
+ try {
+ result = deleteEvents(context, where, null);
+ } catch (SQLException exception) {
+ result = INVALID_ID;
+ } catch (SecurityException exception) {
+ result = INVALID_ID;
+ } catch (IllegalArgumentException exception) {
+ result = INVALID_ID;
+ }
+
+ return result;
+ }
+
+ /**
+ * Delete all events associated with this extension
+ *
+ * @param context The context.
+ * @return The number of events that was deleted or {@link #INVALID_ID} on
+ * failure
+ */
+ public static int deleteAllEvents(Context context) {
+ int result = 0;
+ try {
+ result = deleteEvents(context, null, null);
+ } catch (SQLException exception) {
+ result = INVALID_ID;
+ } catch (SecurityException exception) {
+ result = INVALID_ID;
+ } catch (IllegalArgumentException exception) {
+ result = INVALID_ID;
+ }
+
+ return result;
+ }
+
+ /**
+ * Mark all events as read
+ *
+ * @param context
+ * @return Number of updated rows in event table, INVALID_ID on failure
+ */
+ public static int markAllEventsAsRead(Context context) {
+ int nbrUpdated = 0;
+ try {
+ ContentValues cv = new ContentValues();
+ cv.put(Notification.EventColumns.EVENT_READ_STATUS, true);
+ nbrUpdated = updateEvents(context, cv, null, null);
+ } catch (SQLException exception) {
+ nbrUpdated = INVALID_ID;
+ } catch (SecurityException exception) {
+ nbrUpdated = INVALID_ID;
+ } catch (IllegalArgumentException exception) {
+ nbrUpdated = INVALID_ID;
+ }
+ return nbrUpdated;
+ }
+
+ /**
+ * Add new event to Event table
+ *
+ * @param context Context with permissions to access Notification db
+ * @param eventValues A reference to the notification
+ * com.sonyericsson.extras
+ * .liveware.aef.notification.Notification.
+ * @return Uri to the created event
+ */
+ public static Uri addEvent(final Context context, final ContentValues eventValues) {
+ try {
+ return context.getContentResolver().insert(Notification.Event.URI, eventValues);
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to add event", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to add event", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to add event", e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get source ids associated with extension.
+ *
+ * @param context Context with permissions to access Notification db
+ * @return Source ids, empty array if not found
+ */
+ public static ArrayList getSourceIds(final Context context, boolean enabled) {
+ ArrayList sourceIds = new ArrayList();
+ Cursor cursor = null;
+ String where = Notification.SourceColumns.ENABLED + "=" + (enabled ? "1" : "0");
+ try {
+ cursor = querySources(context, new String[] {
+ Notification.SourceColumns._ID
+ }, where, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ sourceIds.add(cursor.getInt(cursor.getColumnIndex(Notification.SourceColumns._ID)));
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return sourceIds;
+ }
+
+ /**
+ * Get Extension Specific Ids associated with an extension.
+ *
+ * @param context Context with permissions to access Notification db
+ * @return Extension Specific Ids ids for enabled sources, empty array if
+ * not found
+ */
+ public static ArrayList getExtensionSpecificIds(final Context context) {
+ ArrayList extensionSpecificIds = new ArrayList();
+ Cursor cursor = null;
+ try {
+ cursor = querySources(context, new String[] {
+ Notification.SourceColumns.EXTENSION_SPECIFIC_ID
+ }, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ extensionSpecificIds.add(cursor.getString(cursor
+ .getColumnIndex(Notification.SourceColumns.EXTENSION_SPECIFIC_ID)));
+ } while (cursor.moveToNext());
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return extensionSpecificIds;
+ }
+
+ /**
+ * Get all source ids associated with extension
+ *
+ * @param context Context with permissions to access Notification db
+ * @return All source ids, empty array if not found
+ */
+ public static ArrayList getSourceIds(final Context context) {
+ ArrayList sourceIds = new ArrayList();
+ Cursor cursor = null;
+ try {
+ cursor = querySources(context, new String[] {
+ Notification.SourceColumns._ID
+ }, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ do {
+ sourceIds.add(cursor.getLong(cursor
+ .getColumnIndex(Notification.SourceColumns._ID)));
+ } while (cursor.moveToNext());
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sources", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sources", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sources", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return sourceIds;
+ }
+
+ /**
+ * Get friend key associated with event id
+ *
+ * @param context Context with permissions to access Notification db
+ * @param eventId Id of event
+ * @return Event title or null if not found.
+ */
+ public static String getFriendKey(final Context context, long eventId) {
+ Cursor cursor = null;
+ String freindKey = null;
+ try {
+ cursor = queryEvents(context, new String[] {
+ Notification.EventColumns.FRIEND_KEY
+ }, EVENT_ID + " = " + eventId, null, null);
+
+ if (cursor != null && cursor.moveToFirst()) {
+ int titleIndex = cursor.getColumnIndex(Notification.EventColumns.FRIEND_KEY);
+ freindKey = cursor.getString(titleIndex);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return freindKey;
+ }
+
+ /**
+ * Query {@link Notification.SourceEvent#URI}, limit query to affect events
+ * in this extension only. Returned cursor is a join between
+ * {@link Notification.Source#URI} and {@link Notification.Event#URI}.
+ * Queries executed on {@link Notification.Event#URI} can also be executed
+ * by this method if all references to {@link Notification.EventColumns#_ID}
+ * are replaced with event._id
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ *
+ * This method is also convenient when querying events that belongs to a
+ * specific {@link Notification.SourceColumns#EXTENSION_SPECIFIC_ID} since
+ * no extra lookup of the source id is needed.
+ *
+ *
+ * @param context The context
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient. The projection can
+ * contain all columns in {@link Notification.EventColumns} and
+ * all columns in {@link Notification.SourceColumns} except for
+ * {@link Notification.SourceColumns#_ID}
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null
+ * will return all rows for the given URI.
+ * @param selectionArgs Arguments to where String
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use
+ * the default sort order, which may be unordered.
+ * @return A Cursor object, which is positioned before the first entry, or
+ * null
+ */
+ public static Cursor queryEvents(Context context, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ String extensionWhere = getSourcesWhere(context);
+ if (!TextUtils.isEmpty(selection)) {
+ extensionWhere += " AND (" + selection + ")";
+ }
+ return context.getContentResolver().query(Notification.SourceEvent.URI, projection,
+ extensionWhere, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Query {@link Notification.SourceEvent#URI}, limit query to affect events
+ * in this extension only and sources that are enabled. Returned cursor is a
+ * join between {@link Notification.Source#URI} and
+ * {@link Notification.Event#URI}. Queries executed on
+ * {@link Notification.Event#URI} can also be executed by this method if all
+ * references to {@link Notification.EventColumns#_ID} are replaced with
+ * event._id
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ *
+ * This method is also convenient when querying events that belongs to a
+ * specific {@link Notification.SourceColumns#EXTENSION_SPECIFIC_ID} since
+ * no extra lookup of the source id is needed.
+ *
+ *
+ * @param context The context
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient. The projection can
+ * contain all columns in {@link Notification.EventColumns} and
+ * all columns in {@link Notification.SourceColumns} except for
+ * {@link Notification.SourceColumns#_ID}
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null
+ * will return all rows for the given URI.
+ * @param selectionArgs Arguments to where String
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use
+ * the default sort order, which may be unordered.
+ * @return A Cursor object, which is positioned before the first entry, or
+ * null
+ */
+ public static Cursor queryEventsFromEnabledSources(Context context, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ String where = Notification.SourceEventColumns.ENABLED + " = 1";
+ if (!TextUtils.isEmpty(selection)) {
+ where += " AND (" + selection + ")";
+ }
+ return queryEvents(context, projection, where, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Update events, limit update to affect events in this extension only.
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ * @param context The context
+ * @param values The new field values. The key is the column name for the
+ * field. A null value will remove an existing field value.
+ * @param where A filter to apply to rows before updating, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself).
+ * @param selectionArgs Arguments to where String
+ * @return The number of rows updated
+ */
+ public static int updateEvents(Context context, ContentValues values, String where,
+ String[] selectionArgs) {
+ String extensionWhere = getEventsWhere(context);
+ if (!TextUtils.isEmpty(where)) {
+ extensionWhere += " AND (" + where + ")";
+ }
+ return context.getContentResolver().update(Notification.Event.URI, values, extensionWhere,
+ selectionArgs);
+ }
+
+ /**
+ * Delete events, limit delete to affect events in this extension only.
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ * @param context The context
+ * @param where A filter to apply to rows before deleting, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself).
+ * @param selectionArgs Arguments to where String
+ * @return The number of rows deleted
+ */
+ public static int deleteEvents(Context context, String where, String[] selectionArgs) {
+ String extensionWhere = getEventsWhere(context);
+ if (!TextUtils.isEmpty(where)) {
+ extensionWhere += " AND (" + where + ")";
+ }
+ return context.getContentResolver().delete(Notification.Event.URI, extensionWhere,
+ selectionArgs);
+
+ }
+
+ /**
+ * Get where string that limits a queries to {@link Notification.Event#URI}
+ * to affect events that belongs to this extension only
+ *
+ * @param context The context
+ * @return The where string:
+ *
+ * Template: sourceId IN ( sourceId1, sourceId2, ... )
+ */
+ public static String getEventsWhere(Context context) {
+ ArrayList sourceIds = getSourceIds(context);
+ if (sourceIds.size() == 0) {
+ return "0";
+ }
+ // Build where clause
+ StringBuilder whereBuilder = new StringBuilder();
+ whereBuilder.append(Notification.EventColumns.SOURCE_ID + " IN ( ");
+ for (int i = 0; i < sourceIds.size() - 1; i++) {
+ whereBuilder.append(sourceIds.get(i) + ", ");
+ }
+ whereBuilder.append(sourceIds.get(sourceIds.size() - 1));
+ whereBuilder.append(" )");
+ return whereBuilder.toString();
+ }
+
+ /**
+ * Query sources, limit scope to sources in this extension
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ * @param context The context
+ * @param projection A list of which columns to return. Passing null will
+ * return all columns, which is inefficient.
+ * @param selection A filter declaring which rows to return, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself). Passing null
+ * will return all rows for the given URI.
+ * @param selectionArgs Arguments to where String
+ * @param sortOrder How to order the rows, formatted as an SQL ORDER BY
+ * clause (excluding the ORDER BY itself). Passing null will use
+ * the default sort order, which may be unordered.
+ * @return A Cursor object, which is positioned before the first entry, or
+ * null
+ */
+ public static Cursor querySources(Context context, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ String extensionWhere = getSourcesWhere(context);
+ if (!TextUtils.isEmpty(selection)) {
+ extensionWhere += " AND (" + selection + ")";
+ }
+ return context.getContentResolver().query(Notification.Source.URI, projection,
+ extensionWhere, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Update sources, limit scope to sources in this extension
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ * @param context The context
+ * @param values The new field values. The key is the column name for the
+ * field. A null value will remove an existing field value.
+ * @param where A filter to apply to rows before updating, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself).
+ * @param selectionArgs Arguments to where String
+ * @return The number of rows updated
+ */
+ public static int updateSources(Context context, ContentValues values, String where,
+ String[] selectionArgs) {
+ String extensionWhere = getSourcesWhere(context);
+ if (!TextUtils.isEmpty(where)) {
+ extensionWhere += " AND (" + where + ")";
+ }
+ DeviceInfoHelper.removeUnsafeValues(context, values);
+ return context.getContentResolver().update(Notification.Source.URI, values, extensionWhere,
+ selectionArgs);
+ }
+
+ /**
+ * Delete sources, limit scope to sources in this extension
+ *
+ *
+ * This method is mainly aimed for extensions that shares user id and
+ * process in which case they can access and modify data that belongs to
+ * other extensions.
+ *
+ *
+ * Note that no runtime exceptions, such as {@link SecurityException} and
+ * {@link SQLException}, will be handled by this method. Exceptions shall
+ * instead be handled in the calling method if needed.
+ *
+ * @param context The context
+ * @param where A filter to apply to rows before deleting, formatted as an
+ * SQL WHERE clause (excluding the WHERE itself).
+ * @param selectionArgs Arguments to where String
+ * @return The number of rows deleted
+ */
+ public static int deleteSources(Context context, String where, String[] selectionArgs) {
+ String extensionWhere = getSourcesWhere(context);
+ if (!TextUtils.isEmpty(where)) {
+ extensionWhere += " AND (" + where + ")";
+ }
+ return context.getContentResolver().delete(Notification.Source.URI, extensionWhere,
+ selectionArgs);
+
+ }
+
+ /**
+ * Get where string that limits a queries to {@link Notification.Source#URI}
+ * and {@link Notification.SourceEvents#URI} to affect sources and source
+ * events that belongs to this extension only
+ *
+ * @param context The context
+ * @return The where string
+ */
+ public static String getSourcesWhere(Context context) {
+ return Notification.SourceColumns.PACKAGE_NAME + " = '" + context.getPackageName() + "'";
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfo.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfo.java
new file mode 100644
index 0000000..6410ddf
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfo.java
@@ -0,0 +1,397 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Display;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DisplayColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Input;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.InputColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.KeyPad;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.KeyPadColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorType;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorTypeColumns;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.sensor.AccessorySensor;
+import com.sonyericsson.extras.liveware.extension.util.sensor.AccessorySensorType;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The device info class describes a host application device. This class only
+ * contains a subset of the information available for the device.
+ */
+public class DeviceInfo {
+
+ private final Context mContext;
+
+ private final String mHostAppPackageName;
+
+ private final long mId;
+
+ private final int mWidgetWidth;
+
+ private final int mWidgetHeight;
+
+ private final boolean mVibrator;
+
+ private List mDisplays = null;
+
+ private List mSensors = null;
+
+ private List mInputs = null;
+
+ /**
+ * Create device info.
+ *
+ * @param context The context.
+ * @param hostAppPackageName The host application package name.
+ * @param id The device id.
+ * @param widgetWidth The widget width.
+ * @param widgetHeight The widget height.
+ * @param vibrator True if device has a vibrator.
+ */
+ public DeviceInfo(final Context context, final String hostAppPackageName, final long id,
+ final int widgetWidth, final int widgetHeight, final boolean vibrator) {
+ mContext = context;
+ mHostAppPackageName = hostAppPackageName;
+ mId = id;
+ mWidgetWidth = widgetWidth;
+ mWidgetHeight = widgetHeight;
+ mVibrator = vibrator;
+ }
+
+ /**
+ * Get the id.
+ *
+ * @see Registration.DeviceColumns.#_ID
+ *
+ * @return The device id.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Get the widget width.
+ *
+ * @see Registration.DeviceColumns.#WIDGET_IMAGE_WIDTH
+ *
+ * @return The widget width.
+ */
+ public int getWidgetWidth() {
+ return mWidgetWidth;
+ }
+
+ /**
+ * Get the widget height.
+ *
+ * @see Registration.DeviceColumns.#WIDGET_IMAGE_HEIGHT
+ *
+ * @return The widget height.
+ */
+ public int getWidgetHeight() {
+ return mWidgetHeight;
+ }
+
+ /**
+ * Checks if the device has a vibrator.
+ *
+ * @see Registration.DeviceColumns.#VIBRATOR
+ *
+ * @return True if the device has a vibrator.
+ */
+ public boolean hasVibrator() {
+ return mVibrator;
+ }
+
+ /**
+ * Get the displays available.
+ *
+ * @return List with displays.
+ */
+ public List getDisplays() {
+ if (mDisplays != null) {
+ // List of displays already available. Avoid re-reading from
+ // database.
+ return mDisplays;
+ }
+
+ mDisplays = new ArrayList();
+
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(Display.URI, null,
+ DisplayColumns.DEVICE_ID + " = " + mId, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ long displayId = cursor.getLong(cursor.getColumnIndexOrThrow(DisplayColumns._ID));
+ int height = cursor.getInt(cursor
+ .getColumnIndexOrThrow(DisplayColumns.DISPLAY_HEIGHT));
+ int width = cursor.getInt(cursor
+ .getColumnIndexOrThrow(DisplayColumns.DISPLAY_WIDTH));
+ int colors = cursor.getInt(cursor.getColumnIndexOrThrow(DisplayColumns.COLORS));
+ int refreshRate = cursor.getInt(cursor
+ .getColumnIndexOrThrow(DisplayColumns.REFRESH_RATE));
+ int latency = cursor.getInt(cursor.getColumnIndexOrThrow(DisplayColumns.LATENCY));
+ boolean tapTouch = (cursor.getInt(cursor
+ .getColumnIndexOrThrow(DisplayColumns.TAP_TOUCH)) == 1);
+ boolean motionTouch = (cursor.getInt(cursor
+ .getColumnIndexOrThrow(DisplayColumns.MOTION_TOUCH)) == 1);
+ DisplayInfo display = new DisplayInfo(displayId, width, height, colors,
+ refreshRate, latency, tapTouch, motionTouch);
+ mDisplays.add(display);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query displays", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query displays", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query displays", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return mDisplays;
+ }
+
+ /**
+ * Get the sensors available.
+ *
+ * @return List of sensors.
+ */
+ public List getSensors() {
+ if (mSensors != null) {
+ // List of sensors already available. Avoid re-reading from
+ // database.
+ return mSensors;
+ }
+
+ mSensors = new ArrayList();
+
+ Cursor cursor = null;
+
+ try {
+ cursor = mContext.getContentResolver().query(
+ com.sonyericsson.extras.liveware.aef.registration.Registration.Sensor.URI,
+ null, SensorColumns.DEVICE_ID + " = ?", new String[] {
+ Long.toString(mId)
+ }, null);
+ while (cursor != null && cursor.moveToNext()) {
+ int sensorId = cursor.getInt(cursor.getColumnIndexOrThrow(SensorColumns.SENSOR_ID));
+ boolean isInterruptSupported = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.SUPPORTS_SENSOR_INTERRUPT)) == 1;
+ String name = cursor.getString(cursor.getColumnIndexOrThrow(SensorColumns.NAME));
+ int resolution = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.RESOLUTION));
+ int minimumDelay = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.MINIMUM_DELAY));
+ int maximumRange = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.MAXIMUM_RANGE));
+ int typeId = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.SENSOR_TYPE_ID));
+
+ AccessorySensorType type = getSensorType(typeId);
+
+ AccessorySensor sensor = new AccessorySensor(mContext, mHostAppPackageName,
+ sensorId, type, isInterruptSupported, name, resolution, minimumDelay,
+ maximumRange);
+ mSensors.add(sensor);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return mSensors;
+ }
+
+ /**
+ * Get the inputs available.
+ *
+ * @return List of inputs.
+ */
+ public List getInputs() {
+ if (mInputs != null) {
+ // List of inputs already available. Avoid re-reading from
+ // database.
+ return mInputs;
+ }
+
+ mInputs = new ArrayList();
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(Input.URI, null,
+ InputColumns.DEVICE_ID + " = ?", new String[] {
+ Long.toString(mId)
+ }, null);
+ while (cursor != null && cursor.moveToNext()) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(InputColumns._ID));
+ boolean enabled = cursor.getInt(cursor.getColumnIndexOrThrow(InputColumns.ENABLED)) == 1;
+ long keyPadId = cursor.getLong(cursor
+ .getColumnIndexOrThrow(InputColumns.KEY_PAD_ID));
+ KeyPadInfo keyPad = getKeyPad(keyPadId);
+ InputInfo input = new InputInfo(id, enabled, keyPad);
+ mInputs.add(input);
+ }
+
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query inputs", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query inputs", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query inputs", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return mInputs;
+ }
+
+ /**
+ * Get the sensor type.
+ *
+ * @param typeId The sensor type id.
+ * @return The sensor type.
+ */
+ private AccessorySensorType getSensorType(int typeId) {
+ AccessorySensorType type = null;
+ Cursor cursor = null;
+
+ try {
+ cursor = mContext.getContentResolver().query(SensorType.URI, null,
+ SensorTypeColumns._ID + " = ?", new String[] {
+ Integer.toString(typeId)
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ boolean isDelicate = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorTypeColumns.DELICATE_SENSOR_DATA)) == 1;
+ String name = cursor
+ .getString(cursor.getColumnIndexOrThrow(SensorTypeColumns.TYPE));
+ type = new AccessorySensorType(name, isDelicate, typeId);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor type", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor type", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor type", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return type;
+ }
+
+ /**
+ * Get the key pad.
+ *
+ * @param id The key pad id.
+ * @return The key pad.
+ */
+ private KeyPadInfo getKeyPad(long id) {
+ KeyPadInfo keyPad = null;
+ Cursor cursor = null;
+
+ try {
+ cursor = mContext.getContentResolver().query(KeyPad.URI, null,
+ KeyPadColumns._ID + " = ?", new String[] {
+ Long.toString(id)
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ String type = cursor.getString(cursor.getColumnIndexOrThrow(KeyPadColumns.TYPE));
+ keyPad = new KeyPadInfo(id, type);
+ }
+
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query key pad", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query key pad", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query key pad", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return keyPad;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfoHelper.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfoHelper.java
new file mode 100644
index 0000000..90e5c31
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/DeviceInfoHelper.java
@@ -0,0 +1,277 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.text.TextUtils;
+
+import com.dattasmoon.pebble.plugin.R;
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.sensor.AccessorySensor;
+
+/**
+ * This class contains information about the watch
+ */
+public class DeviceInfoHelper {
+ private static final int SMARTWATCH_2_API_LEVEL = 2;
+
+ /**
+ * Checks host app API level and screen size to check if SmartWatch 2 is
+ * supported.
+ *
+ * @param context
+ * The context.
+ * @return true if SmartWatch is supported.
+ */
+ public static boolean isSmartWatch2ApiAndScreenDetected(Context context,
+ String hostAppPackageName) {
+ HostApplicationInfo hostApp = getHostApp(context, hostAppPackageName);
+ if (hostApp == null) {
+ Dbg.d("Host app was null, returning");
+ return false;
+ }
+ // Get screen dimensions, unscaled
+ final int controlSWWidth = getSmartWatch2Width(context);
+ final int controlSWHeight = getSmartWatch2Height(context);
+
+ if (hostApp.getControlApiVersion() >= SMARTWATCH_2_API_LEVEL) {
+ for (DeviceInfo device : RegistrationAdapter.getHostApplication(
+ context, hostAppPackageName).getDevices()) {
+ for (DisplayInfo display : device.getDisplays()) {
+ if (display.sizeEquals(controlSWWidth, controlSWHeight)) {
+ return true;
+ }
+ }
+ }
+ } else {
+ Dbg.d("Host had control API version: "
+ + hostApp.getControlApiVersion() + ", returning");
+ }
+ return false;
+ }
+
+ /**
+ * Checks if host application supports a specific sensor.
+ *
+ * @param context
+ * The context.
+ * @param hostAppPackageName
+ * The package name of the host application
+ * @param sensorType
+ * The sensor type
+ * @return true if the host application supports the sensor
+ */
+ public static boolean isSensorSupported(Context context,
+ String hostAppPackageName, String sensorType) {
+ boolean sensorSupported = false;
+
+ HostApplicationInfo hostApp = getHostApp(context, hostAppPackageName);
+ if (hostApp == null) {
+ Dbg.d("Host app was null, bailing.");
+ } else if (hostApp.getSensorApiVersion() > 0) {
+ for (DeviceInfo device : hostApp.getDevices()) {
+ for (AccessorySensor sensor : device.getSensors()) {
+ if (TextUtils
+ .equals(sensor.getType().getName(), sensorType)) {
+ sensorSupported = true;
+ break;
+ }
+ }
+ }
+ }
+
+ return sensorSupported;
+ }
+
+ /**
+ * Gets the first connected host app
+ *
+ * @param hostAppPackageName
+ * @return host app or null if no host app found
+ */
+ private static HostApplicationInfo getHostApp(Context context,
+ String hostAppPackageName) {
+ HostApplicationInfo hostApp = RegistrationAdapter.getHostApplication(
+ context, hostAppPackageName);
+ return hostApp;
+ }
+
+ /**
+ * Get SmartWatch 2 screen width.
+ *
+ * @param context
+ * The context.
+ * @return width.
+ */
+ private static int getSmartWatch2Width(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_2_control_width);
+ }
+
+ /**
+ * Get SmartWatch 2 screen height.
+ *
+ * @param context
+ * The context.
+ * @return height.
+ */
+ private static int getSmartWatch2Height(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_2_control_height);
+ }
+
+ /**
+ * Array of API keys that are in API 2 but not 1.
+ */
+ private static String[] API_2_KEYS = {
+ Notification.SourceColumns.ACTION_ICON_1,
+ Notification.SourceColumns.ACTION_ICON_2,
+ Notification.SourceColumns.ACTION_ICON_3,
+ Notification.SourceColumns.COLOR,
+ Notification.SourceColumns.SUPPORTS_REFRESH,
+
+ Notification.SourceEventColumns.ACTION_ICON_1,
+ Notification.SourceEventColumns.ACTION_ICON_2,
+ Notification.SourceEventColumns.ACTION_ICON_3,
+ Notification.SourceEventColumns.COLOR,
+
+ Registration.ApiRegistrationColumns.CONTROL_BACK_INTERCEPT,
+ Registration.ApiRegistrationColumns.LOW_POWER_SUPPORT,
+
+ Registration.ExtensionColumns.EXTENSION_48PX_ICON_URI,
+ Registration.ExtensionColumns.LAUNCH_MODE };
+
+ private static int smartConnectLevel = -1;
+
+ /**
+ * Iterates through ContentValues and removes values that are not available
+ * for the given SmartConnect version, regardless of which table the value
+ * is in. Current implementation only supports API levels 1 and 2. May not
+ * be complete.
+ *
+ * @param context
+ * @param apiLevel
+ * The version of the current API level, contentvalues not
+ * supported in this API level will be removed
+ * @param values
+ * the ContentValues to be scanned for unsafe values
+ * @return
+ */
+ static int removeUnsafeValues(Context context, int apiLevel,
+ ContentValues values) {
+ int removedValues = 0;
+
+ if (apiLevel < 2) {
+ for (String key : API_2_KEYS) {
+ if (values.containsKey(key)) {
+ values.remove(key);
+ Dbg.d("Removing " + key + " key from contentvalues");
+ removedValues++;
+ }
+ }
+ }
+ Dbg.e("Removed " + removedValues + " values from contentvalues");
+ return removedValues;
+ }
+
+ /**
+ * Removes contentvalues that don't match the current SmartConnect version.
+ * NOTE: This method can not be called before extension is registered
+ *
+ * @see DeviceInfoHelper#removeUnsafeValues(Context, int, ContentValues)
+ * @param apiLevel
+ * @param values
+ * @return
+ */
+ public static int removeUnsafeValues(Context context, ContentValues values) {
+
+ if (smartConnectLevel == -1) {
+ smartConnectLevel = getSmartConnectVersion(context);
+ }
+ return removeUnsafeValues(context, smartConnectLevel, values);
+ }
+
+ /**
+ * Gets the SmartConnect version. There's no explicit versioning in
+ * SmartConnect, and generally it's not required. This method infers the
+ * version based on database columns.
+ *
+ * @param context
+ * The context.
+ * @return The inferred SmartConnect version
+ */
+ private static int getSmartConnectVersion(final Context context) {
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(
+ Registration.Extension.URI, null, null, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+
+ // The EXTENSION_48PX_ICON_URI colnum was added in HostApp api
+ // level 2. Since
+ // this colnum exists we know we can safely add other colnums
+ // from this API level
+ int lowPowerColnum = cursor
+ .getColumnIndex(Registration.ExtensionColumns.EXTENSION_48PX_ICON_URI);
+ if (lowPowerColnum != -1) {
+ Dbg.d("SmartConnect version 2 or higher detected");
+ return 2;
+ } else {
+ Dbg.d("SmartConnect version 1 detected");
+ return 1;
+ }
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return 1;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/DisplayInfo.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/DisplayInfo.java
new file mode 100644
index 0000000..a03250f
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/DisplayInfo.java
@@ -0,0 +1,180 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+
+/**
+ * The display info describes a host application display.
+ */
+public class DisplayInfo {
+
+ private final long mId;
+
+ private final int mWidth;
+
+ private final int mHeight;
+
+ private final int mColors;
+
+ private final int mRefreshRate;
+
+ private final int mLatency;
+
+ private final boolean mTapTouch;
+
+ private final boolean mMotionTouch;
+
+ /**
+ * Create display info.
+ *
+ * @param id The id.
+ * @param width The width.
+ * @param height The height.
+ * @param colors The colors.
+ * @param refreshRate The refresh rate.
+ * @param latency The latency.
+ * @param tapTouch True if tap touch is supported.
+ * @param motionTouch True if motion touch is supported.
+ */
+ public DisplayInfo(final long id, final int width, final int height, final int colors,
+ final int refreshRate, final int latency, final boolean tapTouch,
+ final boolean motionTouch) {
+ mId = id;
+ mWidth = width;
+ mHeight = height;
+ mColors = colors;
+ mRefreshRate = refreshRate;
+ mLatency = latency;
+ mTapTouch = tapTouch;
+ mMotionTouch = motionTouch;
+ }
+
+ /**
+ * Get the id.
+ *
+ * @see Registration.DisplayColumns.#_ID
+ *
+ * @return The id.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Get the width.
+ *
+ * @see Registration.DisplayColumns.#DISPLAY_WIDTH
+ *
+ * @return The width.
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Get the height.
+ *
+ * @see Registration.DisplayColumns.#DISPLAY_HEIGHT
+ *
+ * @return The height.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Get the number of colors supported by the display.
+ *
+ * @see Registration.DisplayColumns.#COLORS
+ *
+ * @return The number of colors.
+ */
+ public int getColors() {
+ return mColors;
+ }
+
+ /**
+ * Get the refresh rate supported by the display.
+ *
+ * @see Registration.DisplayColumns.#REFRESH_RATE
+ *
+ * @return The refresh rate.
+ */
+ public int getRefreshRate() {
+ return mRefreshRate;
+ }
+
+ /**
+ * Get the display latency.
+ *
+ * @see Registration.DisplayColumns.#LATENCY
+ *
+ * @return The latency.
+ */
+ public int getLatency() {
+ return mLatency;
+ }
+
+ /**
+ * Is tap touch supported.
+ *
+ * @see Registration.DisplayColumns.#TAP_TOUCH
+ *
+ * @return True if tap touch is supported.
+ */
+ public boolean isTapTouch() {
+ return mTapTouch;
+ }
+
+ /**
+ * Is motion touch supported.
+ *
+ * @see Registration.DisplayColumns.#MOTION_TOUCH
+ *
+ * @return True if motion touch is supported.
+ */
+ public boolean isMotionTouch() {
+ return mMotionTouch;
+ }
+
+ /**
+ * Check if the display size is equal to the provided width and height.
+ *
+ * @param width The width to check.
+ * @param height The height to check.
+ * @return True if the display is equal to the provided with and height.
+ */
+ public boolean sizeEquals(int width, int height) {
+ return (mWidth == width && mHeight == height);
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/HostApplicationInfo.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/HostApplicationInfo.java
new file mode 100644
index 0000000..7a2c049
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/HostApplicationInfo.java
@@ -0,0 +1,218 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Device;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The host application class contains information about a host application.
+ */
+public class HostApplicationInfo {
+
+ private final long mId;
+
+ private final String mPackageName;
+
+ private final int mWidgetApiVersion;
+
+ private final int mControlApiVersion;
+
+ private final int mSensorApiVersion;
+
+ private final int mNotificationApiVersion;
+
+ private final int mWidgetRefreshRate;
+
+ private final Context mContext;
+
+ private List mDevices = null;
+
+ /**
+ * Create host application info.
+ *
+ * @param context The context.
+ * @param packageName The package name.
+ * @param id The host application id.
+ * @param widgetApiVersion The widget API version.
+ * @param controlApiVersion The control API version.
+ * @param sensorApiVersion The sensor API version.
+ * @param notificationApiVersion The notification API version.
+ * @param widgetRefreshRate The widget refresh rate.
+ */
+ public HostApplicationInfo(final Context context, final String packageName, final long id,
+ final int widgetApiVersion, final int controlApiVersion, final int sensorApiVersion,
+ final int notificationApiVersion, final int widgetRefreshRate) {
+ mContext = context;
+ mPackageName = packageName;
+ mId = id;
+ mWidgetApiVersion = widgetApiVersion;
+ mControlApiVersion = controlApiVersion;
+ mSensorApiVersion = sensorApiVersion;
+ mNotificationApiVersion = notificationApiVersion;
+ mWidgetRefreshRate = widgetRefreshRate;
+ }
+
+ /**
+ * Get the devices for this host application.
+ *
+ * @return List of the devices.
+ */
+ public List getDevices() {
+ if (mDevices != null) {
+ // List of devices already available. Avoid re-reading from database.
+ return mDevices;
+ }
+
+ mDevices = new ArrayList();
+
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(Device.URI, null,
+ DeviceColumns.HOST_APPLICATION_ID + " = " + mId, null, null);
+ while (cursor != null && cursor.moveToNext()) {
+ long deviceId = cursor.getLong(cursor.getColumnIndexOrThrow(DeviceColumns._ID));
+ int widgetWidth = cursor.getInt(cursor
+ .getColumnIndexOrThrow(DeviceColumns.WIDGET_IMAGE_WIDTH));
+ int widgetHeight = cursor.getInt(cursor
+ .getColumnIndexOrThrow(DeviceColumns.WIDGET_IMAGE_HEIGHT));
+ boolean vibrator = (cursor.getInt(cursor
+ .getColumnIndexOrThrow(DeviceColumns.VIBRATOR)) == 1);
+
+ DeviceInfo device = new DeviceInfo(mContext, mPackageName, deviceId, widgetWidth,
+ widgetHeight, vibrator);
+ mDevices.add(device);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query device", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query device", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query device", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return mDevices;
+ }
+
+ /**
+ * Get the id.
+ *
+ * @see Registration.HostAppColumns.#_ID
+ * @return The id.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Get the widget API version.
+ *
+ * @see Registration.HostAppColumns.#WIDGET_API_VERSION
+ *
+ * @return The widget API version.
+ */
+ public int getWidgetApiVersion() {
+ return mWidgetApiVersion;
+ }
+
+ /**
+ * Get the control API version.
+ *
+ * @see Registration.HostAppColumns.#CONTROL_API_VERSION
+ *
+ * @return The control API version.
+ */
+ public int getControlApiVersion() {
+ return mControlApiVersion;
+ }
+
+ /**
+ * Get the registration API version. Certain Registration values rely on SDK
+ * version 2.0 although they depend on no specific API version. This method
+ * returns the lowest safe API value.
+ * @see Registration.ExtensionColumns.#EXTENSION_48PX_ICON_URI
+ */
+ public int getRegistrationApiVersion() {
+ return mControlApiVersion;
+ }
+
+ /**
+ * Get the sensor API version.
+ *
+ * @see Registration.HostAppColumns.#SENSOR_API_VERSION
+ *
+ * @return The sensor API version.
+ */
+ public int getSensorApiVersion() {
+ return mSensorApiVersion;
+ }
+
+ /**
+ * Get the notification API version.
+ *
+ * @see Registration.HostAppColumns.#NOTIFICATION_API_VERSION
+ *
+ * @return The notification API version.
+ */
+ public int getNotificationApiVersion() {
+ return mNotificationApiVersion;
+ }
+
+ /**
+ * Get the widget refresh rate.
+ *
+ * @see Registration.HostAppColumns.#WIDGET_REFRESH_RATE
+ *
+ * @return The widget refresh rate.
+ */
+ public int getWidgetRefreshRate() {
+ return mWidgetRefreshRate;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/IRegisterCallback.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/IRegisterCallback.java
new file mode 100644
index 0000000..21802e2
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/IRegisterCallback.java
@@ -0,0 +1,48 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+
+
+/**
+ * Callback interface used to notify the result of extension and source
+ * registration
+ */
+public interface IRegisterCallback {
+
+ /**
+ * Callback with result of registration
+ * @param onlySources True if only sources was refreshed. False if full registration.
+ * @param success True on register success, false otherwise
+ */
+ public void onExtensionRegisterResult(boolean onlySources, boolean success);
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/InputInfo.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/InputInfo.java
new file mode 100644
index 0000000..124d441
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/InputInfo.java
@@ -0,0 +1,90 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+
+/**
+ * The input info describes a host application input.
+ */
+public class InputInfo {
+
+ private final long mId;
+
+ private final boolean mEnabled;
+
+ private final KeyPadInfo mKeyPad;
+
+ /**
+ * Create input info.
+ *
+ * @param id The input id.
+ * @param enabled True if input is enabled.
+ * @param keyPad The key pad info.
+ */
+ public InputInfo(long id, boolean enabled, KeyPadInfo keyPad) {
+ mId = id;
+ mEnabled = enabled;
+ mKeyPad = keyPad;
+ }
+
+ /**
+ * Get the id.
+ *
+ * @see Registration.InputColumns.#_ID
+ *
+ * @return The id.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Is the input enabled.
+ *
+ * @see Registration.InputColumns.#ENABLED
+ *
+ * @return True if enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Get the key pad.
+ *
+ * @return The key pad.
+ */
+ public KeyPadInfo getKeyPad() {
+ return mKeyPad;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/KeyPadInfo.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/KeyPadInfo.java
new file mode 100644
index 0000000..627a31e
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/KeyPadInfo.java
@@ -0,0 +1,76 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+/**
+ * The key pad info class describes a host application key pad.
+ */
+public class KeyPadInfo {
+
+ private final long mId;
+
+ private final String mType;
+
+ /**
+ * Create key pad info.
+ *
+ * @param id The id.
+ * @param type The type.
+ */
+ public KeyPadInfo(long id, String type) {
+ mId = id;
+ mType = type;
+ }
+
+ /**
+ * Get the id.
+ *
+ * @see Registration.KeyPadColumns.#_ID
+ *
+ * @return The id.
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Get the type.
+ *
+ * @see Registration.KeyPadColumns.#TYPE
+ *
+ * @return The type.
+ */
+ public String getType() {
+ return mType;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/RegisterExtensionTask.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegisterExtensionTask.java
new file mode 100644
index 0000000..16a5830
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegisterExtensionTask.java
@@ -0,0 +1,688 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.BaseColumns;
+
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.notification.Notification.SourceColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;
+
+import java.util.ArrayList;
+
+/**
+ * Perform extension registration or update in background
+ */
+public class RegisterExtensionTask extends AsyncTask {
+
+ private final Context mContext;
+
+ private final RegistrationInformation mRegistrationInformation;
+
+ private IRegisterCallback mRegisterInterface;
+
+ private final boolean mOnlySources;
+
+ /**
+ * Create register extension task
+ *
+ * @param context The context
+ * @param registrationInformation Information needed during registration
+ * @param registerInterface Registration callback interface
+ * @param onlySources True if only sources shall be refreshed. False if full
+ * registration.
+ */
+ public RegisterExtensionTask(Context context, RegistrationInformation registrationInformation,
+ IRegisterCallback registerInterface, boolean onlySources) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ mContext = context;
+
+ if (registrationInformation == null) {
+ throw new IllegalArgumentException("registrationInformation == null");
+ }
+ mRegistrationInformation = registrationInformation;
+ if (registerInterface == null) {
+ throw new IllegalArgumentException("registerInterface == null");
+ }
+ mRegisterInterface = registerInterface;
+
+ mOnlySources = onlySources;
+ }
+
+ /**
+ * Set register interface. Used to set interface to null to handle the case
+ * when the service is destroyed before onPostExecute is executed.
+ *
+ * @param registerInterface The register interface
+ */
+ public void setRegisterInterface(IRegisterCallback registerInterface) {
+ mRegisterInterface = registerInterface;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (mOnlySources) {
+ try {
+ registerOrUpdateSources();
+ return true;
+ } catch (RegisterExtensionException e) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Source refresh failed", e);
+ }
+ return false;
+ }
+ } else {
+ boolean registrationSuccess = registerOrUpdateExtension();
+ if (registrationSuccess) {
+ if (mRegistrationInformation.getRequiredWidgetApiVersion() > 0
+ || mRegistrationInformation.getRequiredControlApiVersion() > 0) {
+ registerWithAllHostApps();
+ }
+ }
+ return registrationSuccess;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Boolean registrationSuccess) {
+ if (mRegisterInterface != null) {
+ mRegisterInterface.onExtensionRegisterResult(mOnlySources, registrationSuccess);
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ if (Dbg.DEBUG) {
+ Dbg.d("Registration task cancelled");
+ }
+ }
+
+ /**
+ * Register the extension or update the registration if already registered.
+ * This method is called from the the background
+ *
+ * @return True if the extension was registered properly.
+ */
+ private boolean registerOrUpdateExtension() {
+ if (Dbg.DEBUG) {
+ Dbg.d("Start registration of extension.");
+ }
+
+ try {
+ // Register or update extension
+ if (!isRegistered()) {
+ register();
+ if (Dbg.DEBUG) {
+ Dbg.d("Registered extension.");
+ }
+ } else {
+ updateRegistration();
+ if (Dbg.DEBUG) {
+ Dbg.d("Updated extension.");
+ }
+ }
+ if (mRegistrationInformation.getRequiredNotificationApiVersion() > 0) {
+ // Register all sources
+ registerOrUpdateSources();
+ }
+
+ } catch (RegisterExtensionException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to register extension", exception);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Find out if this extension is registered or not
+ *
+ * @return True if registered
+ */
+ private boolean isRegistered() {
+ return ExtensionUtils.getExtensionId(mContext) != ExtensionUtils.INVALID_ID;
+ }
+
+ /**
+ * Register extension. This method is called from the the background
+ *
+ * @param context The context.
+ * @throws RegisterExtensionException
+ */
+ private void register() throws RegisterExtensionException {
+ ContentValues configurationValues = mRegistrationInformation
+ .getExtensionRegistrationConfiguration();
+ try {
+ if (!configurationValues
+ .containsKey(Registration.ExtensionColumns.NOTIFICATION_API_VERSION)) {
+ configurationValues.put(Registration.ExtensionColumns.NOTIFICATION_API_VERSION,
+ mRegistrationInformation.getRequiredNotificationApiVersion());
+ } else {
+ if (configurationValues
+ .getAsInteger(Registration.ExtensionColumns.NOTIFICATION_API_VERSION) != mRegistrationInformation
+ .getRequiredNotificationApiVersion()) {
+ throw new RegisterExtensionException(
+ "NOTIFICATION_API_VERSION did not match getRequiredNotificationApiVersion");
+ }
+ }
+
+ // Package name is not required but many of the SDK utility
+ // methods are dependent of the package name.
+ configurationValues.put(Registration.ExtensionColumns.PACKAGE_NAME,
+ mContext.getPackageName());
+
+ Uri uri = mContext.getContentResolver().insert(Registration.Extension.URI,
+ configurationValues);
+ if (uri == null) {
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, configurationValues);
+ uri = mContext.getContentResolver().insert(Registration.Extension.URI,
+ configurationValues);
+ if (uri == null) {
+ throw new RegisterExtensionException("failed to insert extension");
+ } else {
+ Dbg.e("Extension registered updated after retry!");
+ }
+ }
+ } catch (SQLException exception) {
+ // Registration failed, possibly due to configurationValues that
+ // don't exist in v1 registration table, remove them and retry
+ try {
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, configurationValues);
+ Uri uri = mContext.getContentResolver().insert(Registration.Extension.URI,
+ configurationValues);
+
+ if (uri == null) {
+ throw new RegisterExtensionException("failed to insert extension");
+ } else {
+ Dbg.e("Extension registered updated after retry!");
+
+ }
+ } catch (SQLException e) {
+ logAndThrow("Update source failed", exception);
+ }
+ } catch (SecurityException exception) {
+ logAndThrow("Failed to register", exception);
+ } catch (IllegalArgumentException exception) {
+ // If Liveware Manager is not installed.
+ // When the extension is started from Liveware Manager this should
+ // not happen.
+ logAndThrow("Failed to register. Is Liveware Manager installed?", exception);
+ }
+ }
+
+ /**
+ * Update extension registration. This method is called from the the
+ * background
+ *
+ * @param context The context.
+ * @throws RegisterExtensionException
+ */
+ private void updateRegistration() throws RegisterExtensionException {
+ if (Dbg.DEBUG) {
+ Dbg.d("Updating existing registration.");
+ }
+ String where = Registration.ExtensionColumns.PACKAGE_NAME + " = ?";
+ String[] selectionArgs = new String[] {
+ mContext.getPackageName()
+ };
+ try {
+ ContentValues values = mRegistrationInformation.getExtensionRegistrationConfiguration();
+ DeviceInfoHelper.removeUnsafeValues(mContext, values);
+ mContext.getContentResolver().update(Registration.Extension.URI,
+ values, where,
+ selectionArgs);
+ } catch (SQLException exception) {
+ logAndThrow("Failed to update registration", exception);
+ } catch (SecurityException exception) {
+ logAndThrow("Failed to update registration", exception);
+ } catch (IllegalArgumentException exception) {
+ logAndThrow("Failed to update registration", exception);
+ }
+ }
+
+ /**
+ * Register or update source. This method is called from the the background
+ *
+ * @param extensionSpecificId The source type to register.
+ * @throws RegisterExtensionException
+ */
+ private void registerOrUpdateSources() throws RegisterExtensionException {
+ ArrayList oldExtensionSpecificIds = NotificationUtil
+ .getExtensionSpecificIds(mContext);
+
+ for (ContentValues sourceConfiguration : mRegistrationInformation
+ .getSourceRegistrationConfigurations()) {
+ String extensionSpecificId = (String) sourceConfiguration
+ .get(Notification.SourceColumns.EXTENSION_SPECIFIC_ID);
+ // If we find the source id in the database then we have already
+ // registered.
+ long sourceId = NotificationUtil.getSourceId(mContext, extensionSpecificId);
+
+ // Package name is not required but many of the SDK utility
+ // methods are dependent of the package name.
+ sourceConfiguration.put(SourceColumns.PACKAGE_NAME, mContext.getPackageName());
+
+ if (sourceId == NotificationUtil.INVALID_ID) {
+ sourceId = registerSource(sourceConfiguration);
+ } else {
+ updateSource(sourceConfiguration, sourceId);
+ }
+ if (Dbg.DEBUG) {
+ Dbg.d("SourceType:" + extensionSpecificId + " SourceId:" + sourceId);
+ }
+
+ oldExtensionSpecificIds.remove(extensionSpecificId);
+ }
+
+ // Remove any sources that are no longer used.
+ for (String deletedExtensionSpecificId : oldExtensionSpecificIds) {
+ unregisterSource(deletedExtensionSpecificId);
+ }
+ }
+
+ /**
+ * Register source. This method is called from the the background
+ *
+ * @param context The context.
+ * @param extensionSpecificId The extension specific id.
+ * @return The source id.
+ * @throws RegisterExtensionException
+ */
+ private long registerSource(ContentValues sourceValues) throws RegisterExtensionException {
+ long sourceId = NotificationUtil.INVALID_ID;
+
+ try {
+ DeviceInfoHelper.removeUnsafeValues(mContext, sourceValues);
+ Uri uri = mContext.getContentResolver().insert(Notification.Source.URI, sourceValues);
+ if (uri == null) {
+ throw new RegisterExtensionException("failed to insert source");
+ }
+ sourceId = (int) ContentUris.parseId(uri);
+ } catch (SQLException exception) {
+ logAndThrow("Register source failed", exception);
+ } catch (SecurityException exception) {
+ logAndThrow("Register source failed", exception);
+ } catch (IllegalArgumentException exception) {
+ logAndThrow("Register source failed", exception);
+ }
+
+ return sourceId;
+ }
+
+ /**
+ * Update source information. This method is called from the the background
+ *
+ * @param context The context.
+ * @param extensionSpecificId The extension specific id.
+ * @param sourceId The source id.
+ * @throws RegisterExtensionException
+ */
+ private void updateSource(ContentValues sourceValues, long sourceId)
+ throws RegisterExtensionException {
+
+ try {
+ DeviceInfoHelper.removeUnsafeValues(mContext, sourceValues);
+ int result = NotificationUtil.updateSources(mContext, sourceValues, BaseColumns._ID
+ + " = " + sourceId, null);
+
+ if (result != 1) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to update source");
+ }
+ }
+ } catch (SQLException exception) {
+ logAndThrow("Update source failed", exception);
+ } catch (SecurityException exception) {
+ logAndThrow("Update source failed", exception);
+ } catch (IllegalArgumentException exception) {
+ logAndThrow("Update source failed", exception);
+ }
+ }
+
+ /**
+ * Unregister source.
+ *
+ * @param sourceType The source type.
+ * @throws RegisterExtensionException
+ */
+ private void unregisterSource(String extensionSpecificId) throws RegisterExtensionException {
+ try {
+ int noOfDeletedRows = NotificationUtil.deleteSources(mContext,
+ SourceColumns.EXTENSION_SPECIFIC_ID + "=" + "'" + extensionSpecificId + "'",
+ null);
+ if (noOfDeletedRows == 0) {
+ if (Dbg.DEBUG) {
+ Dbg.d("Source was already unregistered: " + extensionSpecificId);
+ }
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.d("Unregistered source: " + extensionSpecificId);
+ }
+ }
+ } catch (SQLException exception) {
+ logAndThrow("Unregister source failed", exception);
+ } catch (SecurityException exception) {
+ logAndThrow("Unregister source failed", exception);
+ } catch (IllegalArgumentException exception) {
+ logAndThrow("Update source failed", exception);
+ }
+ }
+
+ /**
+ * Register with all host applications that supports the extension widget
+ * and/or control requirements. This method is called from the the
+ * background
+ *
+ * @param widgetReceiver The widget receiver for widget events. Null if no
+ * widget functionality.
+ * @param controlReceiver The control receiver for control events. Null if
+ * no control functionality.
+ */
+ private void registerWithAllHostApps() {
+ Cursor cursor = null;
+ try {
+ cursor = mContext.getContentResolver().query(
+ Registration.HostApp.URI,
+ new String[] {
+ Registration.HostAppColumns._ID,
+ Registration.HostAppColumns.PACKAGE_NAME,
+ Registration.HostAppColumns.WIDGET_API_VERSION,
+ Registration.HostAppColumns.CONTROL_API_VERSION,
+ Registration.HostAppColumns.SENSOR_API_VERSION,
+ Registration.HostAppColumns.NOTIFICATION_API_VERSION,
+ Registration.HostAppColumns.WIDGET_REFRESH_RATE
+ }, null, null, null);
+ if (cursor == null) {
+ if (Dbg.DEBUG) {
+ Dbg.e("checkHostAppRegistration: cursor==null");
+ }
+ return;
+ }
+ if (cursor.getCount() == 0) {
+ // No host apps available.
+ return;
+ }
+
+ // Loop through the host apps.
+ cursor.moveToFirst();
+ int packageColumnIndex = cursor
+ .getColumnIndex(Registration.HostAppColumns.PACKAGE_NAME);
+ int hostAppIdColumnIndex = cursor.getColumnIndex(Registration.HostAppColumns._ID);
+ int widgetApiColumnIndex = cursor
+ .getColumnIndex(Registration.HostAppColumns.WIDGET_API_VERSION);
+ int controlApiColumnIndex = cursor
+ .getColumnIndex(Registration.HostAppColumns.CONTROL_API_VERSION);
+ int sensorApiColumnIndex = cursor
+ .getColumnIndex(Registration.HostAppColumns.SENSOR_API_VERSION);
+ int notificationApiColumnIndex = cursor
+ .getColumnIndexOrThrow(Registration.HostAppColumns.NOTIFICATION_API_VERSION);
+ int widgetRefreshRateColumnIndex = cursor
+ .getColumnIndexOrThrow(Registration.HostAppColumns.WIDGET_REFRESH_RATE);
+ while (!cursor.isAfterLast()) {
+ String packageName = cursor.getString(packageColumnIndex);
+ long hostAppId = cursor.getLong(hostAppIdColumnIndex);
+ int widgetApiVersion = cursor.getInt(widgetApiColumnIndex);
+ int controlApiVersion = cursor.getInt(controlApiColumnIndex);
+ int sensorApiVersion = cursor.getInt(sensorApiColumnIndex);
+ int notificationApiVersion = cursor.getInt(notificationApiColumnIndex);
+ int widgetRefreshRate = cursor.getInt(widgetRefreshRateColumnIndex);
+
+ HostApplicationInfo hostApplication = new HostApplicationInfo(mContext,
+ packageName, hostAppId, widgetApiVersion, controlApiVersion,
+ sensorApiVersion, notificationApiVersion, widgetRefreshRate);
+
+ boolean widgetSupported = mRegistrationInformation.isSupportedWidgetAvailable(
+ mContext, hostApplication);
+ boolean controlSupported = mRegistrationInformation.isSupportedControlAvailable(
+ mContext, hostApplication);
+ boolean sensorSupported = mRegistrationInformation.isSupportedSensorAvailable(
+ mContext, hostApplication);
+
+ // If widget, control or sensor was supported then register with
+ // the host app.
+ if (widgetSupported || controlSupported || sensorSupported) {
+ registerApiRegistration(hostApplication, packageName,
+ isHostAppRegistered(packageName), widgetSupported, controlSupported,
+ sensorSupported,
+ mRegistrationInformation.controlInterceptsBackButton(),
+ mRegistrationInformation.supportsLowPowerMode());
+ }
+
+ cursor.moveToNext();
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("registerWithAllHostApps: " + exception.getMessage());
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("registerWithAllHostApps: " + exception.getMessage());
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("registerWithAllHostApps: " + exception.getMessage());
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ }
+
+ /**
+ * Checks if the extension is registered with a host application. This
+ * method is called from the the background
+ *
+ * @param packageName The package name of the host application.
+ * @return True if the extension is registered with the host application.
+ */
+ private boolean isHostAppRegistered(String packageName) {
+ Cursor cursor = null;
+ boolean isRegistered = false;
+ long extensionId = ExtensionUtils.getExtensionId(mContext);
+ String selection = Registration.ApiRegistrationColumns.EXTENSION_ID + " = " + extensionId
+ + " AND " + Registration.ApiRegistrationColumns.HOST_APPLICATION_PACKAGE + " = ?";
+ String[] selectionArgs = new String[] {
+ packageName
+ };
+
+ try {
+ cursor = mContext.getContentResolver().query(Registration.ApiRegistration.URI,
+ new String[] {
+ Registration.ApiRegistrationColumns.HOST_APPLICATION_PACKAGE
+ },
+ selection, selectionArgs, null);
+ if (cursor != null) {
+ isRegistered = (cursor.getCount() > 0);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return isRegistered;
+ }
+
+ /**
+ * Register our extension with a host application. Override this to provide
+ * extension specific implementation. This method is called from the the
+ * background
+ *
+ * @param packageName The package name of host application.
+ * @param isRegistered true if already registered.
+ * @param widgetApiVersionSupported True if widget registration.
+ * @param controlApiVersionSupported True if control registration.
+ * @return True if registration was successful.
+ */
+ private boolean registerApiRegistration(HostApplicationInfo hostApplication,
+ String packageName, boolean isRegistered,
+ boolean widgetApiVersionSupported, boolean controlApiVersionSupported,
+ boolean sensorApiVersionSupported, boolean controlInterceptsBack,
+ boolean lowPowerSupport) {
+ if (Dbg.DEBUG) {
+ Dbg.d("Register API registration: " + packageName);
+ }
+ ContentValues values = new ContentValues();
+ values.put(Registration.ApiRegistrationColumns.HOST_APPLICATION_PACKAGE, packageName);
+ if (widgetApiVersionSupported) {
+ // The api version inserted is based on either the supported level
+ // in host app
+ // or the target API version, whichever is lower.
+ int apiVersion = Math.min(mRegistrationInformation.getTargetWidgetApiVersion(),
+ hostApplication.getWidgetApiVersion());
+ values.put(Registration.ApiRegistrationColumns.WIDGET_API_VERSION, apiVersion);
+ } else {
+ values.put(Registration.ApiRegistrationColumns.WIDGET_API_VERSION, 0);
+ }
+ if (controlApiVersionSupported) {
+ int apiVersion = Math.min(mRegistrationInformation.getTargetControlApiVersion(),
+ hostApplication.getControlApiVersion());
+ values.put(Registration.ApiRegistrationColumns.CONTROL_API_VERSION, apiVersion);
+ } else {
+ values.put(Registration.ApiRegistrationColumns.CONTROL_API_VERSION, 0);
+ }
+ if (sensorApiVersionSupported) {
+ int apiVersion = Math.min(mRegistrationInformation.getTargetSensorApiVersion(),
+ hostApplication.getControlApiVersion());
+ values.put(Registration.ApiRegistrationColumns.SENSOR_API_VERSION, apiVersion);
+ } else {
+ values.put(Registration.ApiRegistrationColumns.SENSOR_API_VERSION, 0);
+ }
+ if (controlInterceptsBack) {
+ values.put(Registration.ApiRegistrationColumns.CONTROL_BACK_INTERCEPT,
+ mRegistrationInformation.controlInterceptsBackButton());
+ } else {
+ values.put(Registration.ApiRegistrationColumns.CONTROL_BACK_INTERCEPT, 0);
+ }
+ if (lowPowerSupport) {
+ values.put(Registration.ApiRegistrationColumns.LOW_POWER_SUPPORT,
+ mRegistrationInformation.supportsLowPowerMode());
+ } else {
+ values.put(Registration.ApiRegistrationColumns.LOW_POWER_SUPPORT, 0);
+ }
+
+ boolean res = false;
+ long extensionId = ExtensionUtils.getExtensionId(mContext);
+ if (!isRegistered) {
+ values.put(Registration.ApiRegistrationColumns.EXTENSION_ID, extensionId);
+ Uri uri = null;
+ try {
+
+ uri = mContext.getContentResolver()
+ .insert(Registration.ApiRegistration.URI, values);
+
+ if (uri == null) {
+ // It's possible the registration did nothing because of an
+ // old
+ // database. Let's remove all API 2 values and try again
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, values);
+ uri = mContext.getContentResolver()
+ .insert(Registration.ApiRegistration.URI, values);
+ }
+ } catch (SQLException exception) {
+ // It's possible the registration failed with an exception
+ // because of an old database. Let's remove all API 2 values and
+ // try again
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, values);
+ uri = mContext.getContentResolver()
+ .insert(Registration.ApiRegistration.URI, values);
+ }
+ res = uri != null;
+ } else {
+ long _id = ExtensionUtils.getRegistrationId(mContext, packageName, extensionId);
+
+ int rows = 0;
+ try {
+ rows = mContext.getContentResolver().update(
+ ContentUris.withAppendedId(Registration.ApiRegistration.URI, _id), values,
+ null, null);
+ if (rows == 0) {
+ // It's possible the registration update failed because of
+ // an old database. Let's remove all API 2 values and try again
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, values);
+ rows = mContext.getContentResolver().update(
+ ContentUris.withAppendedId(Registration.ApiRegistration.URI, _id),
+ values,
+ null, null);
+ }
+ } catch (SQLException exception) {
+ // It's possible the registration update failed because of an
+ // old database. Let's remove all API 2 values and try again
+ DeviceInfoHelper.removeUnsafeValues(mContext, 1, values);
+ rows = mContext.getContentResolver().update(
+ ContentUris.withAppendedId(Registration.ApiRegistration.URI, _id), values,
+ null, null);
+ }
+ res = rows > 0;
+ }
+ return res;
+ }
+
+ /**
+ * Write to log and throw exception This method is called from the the
+ * background
+ *
+ * @param text Text to write to log
+ * @param exception exception to throw
+ * @throws RegisterExtensionException
+ */
+ private void logAndThrow(String text, Exception exception) throws RegisterExtensionException {
+ if (Dbg.DEBUG) {
+ Dbg.e(text, exception);
+ }
+ throw new RegisterExtensionException(text);
+ }
+
+ private static class RegisterExtensionException extends Exception {
+
+ private static final long serialVersionUID = 8351396734279924253L;
+
+ public RegisterExtensionException(String string) {
+ super(string);
+ }
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationAdapter.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationAdapter.java
new file mode 100644
index 0000000..d637e1a
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationAdapter.java
@@ -0,0 +1,101 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostApp;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostAppColumns;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+
+/**
+ * The registration adapter is used to access the registration content provider.
+ */
+public class RegistrationAdapter {
+
+ /**
+ * Get host application.
+ *
+ * @param context The context.
+ * @param packageName The host application package name.
+ * @return The host application.
+ */
+ public static HostApplicationInfo getHostApplication(final Context context,
+ final String packageName) {
+ if (packageName == null) {
+ return null;
+ }
+ Cursor cursor = null;
+ try {
+ cursor = context.getContentResolver().query(HostApp.URI, null,
+ HostAppColumns.PACKAGE_NAME + " = ?", new String[] {
+ packageName
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(HostAppColumns._ID));
+ int widgetApiVersion = cursor.getInt(cursor
+ .getColumnIndexOrThrow(HostAppColumns.WIDGET_API_VERSION));
+ int controlApiVersion = cursor.getInt(cursor
+ .getColumnIndexOrThrow(HostAppColumns.CONTROL_API_VERSION));
+ int sensorApiVersion = cursor.getInt(cursor
+ .getColumnIndexOrThrow(HostAppColumns.SENSOR_API_VERSION));
+ int notificationApiVersion = cursor.getInt(cursor
+ .getColumnIndexOrThrow(HostAppColumns.NOTIFICATION_API_VERSION));
+ int widgetRefreshRate = cursor.getInt(cursor
+ .getColumnIndexOrThrow(HostAppColumns.WIDGET_REFRESH_RATE));
+ return new HostApplicationInfo(context, packageName, id, widgetApiVersion,
+ controlApiVersion, sensorApiVersion, notificationApiVersion,
+ widgetRefreshRate);
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host application", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationInformation.java b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationInformation.java
new file mode 100644
index 0000000..b82e603
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/registration/RegistrationInformation.java
@@ -0,0 +1,331 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.registration;
+
+import android.content.ContentValues;
+import android.content.Context;
+
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionService;
+import com.sonyericsson.extras.liveware.extension.util.sensor.AccessorySensor;
+
+/**
+ * Provides information needed during extension registration
+ */
+public abstract class RegistrationInformation {
+
+ public static int API_NOT_REQUIRED = 0;
+
+ /**
+ * Get the required notifications API version
+ *
+ * @see Registration.ExtensionColumns#NOTIFICATION_API_VERSION
+ * @see #getSourceRegistrationConfigurations
+ * @see ExtensionService#onViewEvent
+ * @see ExtensionService#onRefreshRequest
+ * @return Required notification API version, or 0 if not supporting
+ * notification.
+ */
+ public abstract int getRequiredNotificationApiVersion();
+
+ /**
+ * Get the extension registration information.
+ *
+ * @see ExtensionService#onRegisterResult
+ * @return The registration configuration.
+ */
+ public abstract ContentValues getExtensionRegistrationConfiguration();
+
+ /**
+ * Get all source registration configurations. The extensions specific id
+ * must be set if there are more than one source. Override this method if
+ * this is a notification extension
+ *
+ * @see Notification.SourceColumns#EXTENSION_SPECIFIC_ID
+ * @see #getRequiredNotificationApiVersion()
+ * @param extensionSpecificId The extension specific id.
+ * @return The source registration information.
+ */
+ public ContentValues[] getSourceRegistrationConfigurations() {
+ throw new IllegalArgumentException(
+ "getSourceRegistrationConfiguration() not implemented. Notification extensions must override this method");
+ }
+
+ /**
+ * Checks if the widget size is supported. Override this to provide
+ * extension specific implementation.
+ *
+ * @param width The widget width.
+ * @param height The widget height.
+ * @return True if the widget size is supported.
+ */
+ public boolean isWidgetSizeSupported(int width, int height) {
+ throw new IllegalArgumentException(
+ "isWidgetSizeSupported() not implemented. Widget extensions must override this method");
+ }
+
+ /**
+ * Get the required widget API version
+ *
+ * @see #isWidgetSizeSupported
+ * @see ExtensionService#createWidgetExtension
+ * @see Registration.ApiRegistrationColumns#WIDGET_API_VERSION
+ * @return Required API widget version, or 0 if not supporting widget.
+ */
+ abstract public int getRequiredWidgetApiVersion();
+
+ /**
+ * Get the target widget API version
+ *
+ * @see #isWidgetSizeSupported
+ * @see ExtensionService#createWidgetExtension
+ * @see Registration.ApiRegistrationColumns#WIDGET_API_VERSION
+ * @return Required API widget version, or 0 if not supporting widget.
+ * Returns required widget API version by default.
+ */
+ public int getTargetWidgetApiVersion() {
+ return getRequiredWidgetApiVersion();
+ }
+
+ /**
+ * Checks if the display size is supported.
+ *
+ * @param width The display width.
+ * @param height The display height.
+ * @see ExtensionService#createControlExtension
+ * @return True if the display size is supported.
+ */
+ public boolean isDisplaySizeSupported(final int width, final int height) {
+ throw new IllegalArgumentException(
+ "isDisplaySizeSupported() not implemented. Control extensions must override this method");
+ }
+
+ /**
+ * Get the required control API version
+ *
+ * @see #isDisplaySizeSupported
+ * @see ExtensionService#createControlExtension
+ * @see Registration.ApiRegistrationColumns#CONTROL_API_VERSION
+ * @return Required API control version, or 0 if not supporting control.
+ */
+ abstract public int getRequiredControlApiVersion();
+
+ /**
+ * Get the target control API version
+ *
+ * @see #isDisplaySizeSupported
+ * @see ExtensionService#createControlExtension
+ * @see Registration.ApiRegistrationColumns#CONTROL_API_VERSION
+ * @return Required API control version, or 0 if not supporting control.
+ * Returns required control API version by default.
+ */
+ public int getTargetControlApiVersion() {
+ return getRequiredControlApiVersion();
+ }
+
+ /**
+ * Check if the sensor is supported.
+ *
+ * @param sensor The sensor.
+ * @return True if sensor is supported.
+ */
+ public boolean isSensorSupported(final AccessorySensor sensor) {
+ throw new IllegalArgumentException(
+ "isSensorSupported() not implemented. Sensor extensions must override this method");
+
+ }
+
+ /**
+ * Get the required sensor API version
+ *
+ * @see #isDisplaySizeSupported
+ * @see Registration.ApiRegistrationColumns#SENSOR_API_VERSION
+ * @return Required API sensor version, or 0 if not supporting sensor.
+ */
+ abstract public int getRequiredSensorApiVersion();
+
+ /**
+ * Get the target sensor API version
+ *
+ * @see #isDisplaySizeSupported
+ * @see #getRequiredSensorApiVersion()
+ * @see Registration.ApiRegistrationColumns#SENSOR_API_VERSION
+ * @return Target API sensor version, or 0 if not supporting sensor. Returns
+ * required sensor API version by default.
+ */
+ public int getTargetSensorApiVersion() {
+ return getRequiredSensorApiVersion();
+ }
+
+ /**
+ * Return true if low power mode is supported by the control extension.
+ *
+ * @return True if low power mode is supported
+ */
+ public boolean supportsLowPowerMode() {
+ return false;
+ }
+
+ /**
+ * Return true if the control extension wants to intercept the back button
+ * of the connected accessory
+ *
+ * @return True if extension wants to intercept back button
+ */
+ public boolean controlInterceptsBackButton() {
+ return false;
+ }
+
+ /**
+ * Return true if the sources shall be updated when the extension service is
+ * created. This might be handy if the extension use dynamic sources and the
+ * source can change when the extension service is not running.
+ *
+ * @return True if the source registration shall be update when the
+ * extension service is created.
+ */
+ public boolean isSourcesToBeUpdatedAtServiceCreation() {
+ return false;
+ }
+
+ /**
+ * Check if widget shall be supported for this host application by checking
+ * that the host application has device with a supported widget size. This
+ * method can be override to provide extension specific implementations.
+ *
+ * @param context The context.
+ * @param hostApplication The host application.
+ * @return True if widget shall be supported.
+ */
+ public boolean isSupportedWidgetAvailable(final Context context,
+ final HostApplicationInfo hostApplication) {
+ if (getRequiredWidgetApiVersion() == API_NOT_REQUIRED) {
+ return false;
+ }
+
+ if (hostApplication.getWidgetApiVersion() == 0) {
+ return false;
+ }
+
+ if (getRequiredWidgetApiVersion() > hostApplication.getWidgetApiVersion()) {
+ if (Dbg.DEBUG) {
+ Dbg.w("isSupportedWidgetAvailable: required widget API version not supported");
+ }
+ return false;
+ }
+
+ for (DeviceInfo device : hostApplication.getDevices()) {
+ if (isWidgetSizeSupported(device.getWidgetWidth(), device.getWidgetHeight())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if sensor shall be supported for this host application by checking
+ * if the host application has at least one supported sensor. This method
+ * can be override to provide extension specific implementations.
+ *
+ * @param context The context.
+ * @param hostApplication The host application.
+ * @return True if sensor shall be supported.
+ */
+ public boolean isSupportedSensorAvailable(final Context context,
+ final HostApplicationInfo hostApplication) {
+ if (getRequiredSensorApiVersion() == API_NOT_REQUIRED) {
+ return false;
+ }
+
+ if (hostApplication.getSensorApiVersion() == 0) {
+ return false;
+ }
+
+ if (getRequiredSensorApiVersion() > hostApplication.getSensorApiVersion()) {
+ if (Dbg.DEBUG) {
+ Dbg.w("isSupportedSensorAvailable: required sensor API version not supported");
+ }
+ return false;
+ }
+
+ for (DeviceInfo device : hostApplication.getDevices()) {
+ for (AccessorySensor sensor : device.getSensors()) {
+ if (isSensorSupported(sensor)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if control shall be supported for this host application by checking
+ * that the host application has a device with a supported display size.
+ * This method can be override to provide extension specific
+ * implementations.
+ *
+ * @param context The context.
+ * @param hostApplication The host application.
+ * @return True if control shall be supported.
+ */
+ public boolean isSupportedControlAvailable(final Context context,
+ final HostApplicationInfo hostApplication) {
+ if (getRequiredControlApiVersion() == API_NOT_REQUIRED) {
+ return false;
+ }
+
+ if (hostApplication.getControlApiVersion() == 0) {
+ return false;
+ }
+
+ if (getRequiredControlApiVersion() > hostApplication.getControlApiVersion()) {
+ if (Dbg.DEBUG) {
+ Dbg.w("isSupportedControlAvailable: required control API version not supported");
+ }
+ return false;
+ }
+
+ for (DeviceInfo device : hostApplication.getDevices()) {
+ for (DisplayInfo display : device.getDisplays()) {
+ if (isDisplaySizeSupported(display.getWidth(), display.getHeight())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensor.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensor.java
new file mode 100644
index 0000000..bccf392
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensor.java
@@ -0,0 +1,400 @@
+/*
+ Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+ Copyright (c) 2012 Sony Mobile Communications AB.
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.LocalServerSocket;
+import android.os.Handler;
+import android.os.Message;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.sensor.Sensor;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * The accessory sensor class is used to interact with a sensor on an accessory.
+ */
+public class AccessorySensor {
+
+ private final Context mContext;
+
+ private final String mHostAppPackageName;
+
+ private final int mSensorId;
+
+ private final AccessorySensorType mType;
+
+ private final boolean mIsInterruptModeSupported;
+
+ private final String mName;
+
+ private final int mResolution;
+
+ private final int mMinimumDelay;
+
+ private final int mMaximumRange;
+
+ private final String mSocketName;
+
+ private ServerThread mServerThread;
+
+ private LocalServerSocket mLocalServerSocket;
+
+ private int mSensorRate;
+
+ private int mInterruptMode;
+
+ private AccessorySensorEventListener mListener = null;
+
+ /**
+ * Create accessory sensor. This constructor is normally not called
+ * directly. Instead it is created from AccessorySensorManager or
+ * DeviceInfo.
+ *
+ * @param context The context.
+ * @param hostAppPackageName The host application package name.
+ * @param sensorId The sensor id.
+ * @param type The sensor type.
+ * @param isInterruptSupported True if interrupt mode is supported.
+ * @param name The name.
+ * @param resolution The resolution.
+ * @param minimumDelay The minimum delay.
+ * @param maximumRange The maximum range.
+ */
+ public AccessorySensor(final Context context, final String hostAppPackageName,
+ final int sensorId, final AccessorySensorType type, final boolean isInterruptSupported,
+ final String name, final int resolution, final int minimumDelay, final int maximumRange) {
+ mContext = context;
+ mHostAppPackageName = hostAppPackageName;
+ mSensorId = sensorId;
+ mType = type;
+ mIsInterruptModeSupported = isInterruptSupported;
+ mName = name;
+ mResolution = resolution;
+ mMinimumDelay = minimumDelay;
+ mMaximumRange = maximumRange;
+ mSocketName = hostAppPackageName + "." + type.getName();
+ }
+
+ /**
+ * Register a sensor event listener. It is only possible to have one
+ * listener per sensor.
+ *
+ * @param listener The event listener.
+ * @param sensorRate The sensor rate.
+ * @param interruptMode The interrupt mode.
+ */
+ public void registerListener(final AccessorySensorEventListener listener, final int sensorRate,
+ final int interruptMode) throws AccessorySensorException {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener == null");
+ }
+
+ mListener = listener;
+ mSensorRate = sensorRate;
+ mInterruptMode = interruptMode;
+
+ openSocket();
+ }
+
+ /**
+ * Register a sensor event listener that gets new data when the sensor has
+ * new data. It is only possible to have one listener per sensor.
+ *
+ * @param listener The event listener.
+ */
+ public void registerInterruptListener(final AccessorySensorEventListener listener)
+ throws AccessorySensorException {
+
+ if (!mIsInterruptModeSupported) {
+ throw new IllegalStateException("Interrupt mode not supported");
+ }
+
+ // Rate is ignored in interrupt mode.
+ registerListener(listener, Sensor.SensorRates.SENSOR_DELAY_NORMAL,
+ Sensor.SensorInterruptMode.SENSOR_INTERRUPT_ENABLED);
+ }
+
+ /**
+ * Register a sensor event listener that gets new data at a fixed rate. It
+ * is only possible to have one listener per sensor.
+ *
+ * @param listener The event listener.
+ * @param sensorRate Any one of the constants defined in the
+ * Sensor.SensorRate interface.
+ * @see Sensor.SensorRates
+ */
+ public void registerFixedRateListener(final AccessorySensorEventListener listener,
+ int sensorRate) throws AccessorySensorException {
+ registerListener(listener, sensorRate, Sensor.SensorInterruptMode.SENSOR_INTERRUPT_DISABLED);
+ }
+
+ /**
+ * Unregister sensor event listener.
+ */
+ public void unregisterListener() {
+ mListener = null;
+
+ closeSocket();
+ }
+
+ /**
+ * Get the sensor id.
+ *
+ * @see Registration.SensorColumns.#SENSOR_ID
+ * @return The sensor id.
+ */
+ public int getSensorId() {
+ return mSensorId;
+ }
+
+ /**
+ * Get the sensor type.
+ *
+ * @return The sensor type.
+ */
+ public AccessorySensorType getType() {
+ return mType;
+ }
+
+ /**
+ * Is interrupt mode supported.
+ *
+ * @see Registration.SensorColumns.#SUPPORTS_SENSOR_INTERRUPT
+ * @return True if interrupt mode is supported.
+ */
+ public boolean isInterruptModeSupported() {
+ return mIsInterruptModeSupported;
+ }
+
+ /**
+ * Get the sensor name.
+ *
+ * @see Registration.SensorColumns.#NAME
+ * @return The sensor name.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Get the sensor resolution.
+ *
+ * @see Registration.SensorColumns.#RESOLUTION
+ * @return The sensor resolution.
+ */
+ public int getResolution() {
+ return mResolution;
+ }
+
+ /**
+ * Get the minimum delay.
+ *
+ * @see Registration.SensorColumns.#MINIMUM_DELAY
+ * @return The minimum delay.
+ */
+ public int getMinimumDelay() {
+ return mMinimumDelay;
+ }
+
+ /**
+ * Get the maximum range.
+ *
+ * @see Registration.SensorColumns.#MAXIMUM_RANGE
+ * @return The maximum range.
+ */
+ public int getMaximumRange() {
+ return mMaximumRange;
+ }
+
+ /**
+ * Create socket to be able to read sensor data
+ */
+ private void openSocket() throws AccessorySensorException {
+ try {
+ // Open socket
+ mLocalServerSocket = new LocalServerSocket(mSocketName);
+
+ // Stop server listening thread if running
+ if (mServerThread != null) {
+ mServerThread.interrupt();
+ mServerThread = null;
+ }
+
+ // Start server listening thread
+ mServerThread = new ServerThread(new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ AccessorySensorEvent accessorySensorEvent = (AccessorySensorEvent)msg.obj;
+ if (accessorySensorEvent != null && mListener != null) {
+ mListener.onSensorEvent(accessorySensorEvent);
+ }
+ }
+ });
+ mServerThread.start();
+
+ // Send intent to Aha
+ sendSensorStartListeningIntent();
+ } catch (IOException e) {
+ if (Dbg.DEBUG) {
+ Dbg.e(e.getMessage(), e);
+ }
+ throw new AccessorySensorException(e.getMessage());
+ }
+ }
+
+ /**
+ * Close socket to be able to read sensor data
+ */
+ private void closeSocket() {
+ // Close socket
+ if (mLocalServerSocket != null) {
+ try {
+ mLocalServerSocket.close();
+ mLocalServerSocket = null;
+ } catch (IOException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w(e.getMessage(), e);
+ }
+ }
+ }
+
+ // Stop thread
+ if (mServerThread != null) {
+ mServerThread.interrupt();
+ mServerThread = null;
+ }
+
+ // Send intent to Aha
+ sendSensorStopListeningIntent();
+ }
+
+ /**
+ * Send start listening intent to host application
+ *
+ * @see Sensor.Intents#SENSOR_REGISTER_LISTENER_INTENT
+ */
+ private void sendSensorStartListeningIntent() {
+ Intent i = new Intent(Sensor.Intents.SENSOR_REGISTER_LISTENER_INTENT);
+ i.putExtra(Sensor.Intents.EXTRA_SENSOR_ID, mSensorId);
+ i.putExtra(Sensor.Intents.EXTRA_SENSOR_LOCAL_SERVER_SOCKET_NAME, mSocketName);
+ i.putExtra(Sensor.Intents.EXTRA_SENSOR_REQUESTED_RATE, mSensorRate);
+ i.putExtra(Sensor.Intents.EXTRA_SENSOR_INTERRUPT_MODE, mInterruptMode);
+ sendToHostApp(i);
+ }
+
+ /**
+ * Send stop listening intent to host application
+ *
+ * @see Sensor.Intents#SENSOR_UNREGISTER_LISTENER_INTENT
+ */
+ private void sendSensorStopListeningIntent() {
+ Intent i = new Intent(Sensor.Intents.SENSOR_UNREGISTER_LISTENER_INTENT);
+ i.putExtra(Sensor.Intents.EXTRA_SENSOR_ID, mSensorId);
+ sendToHostApp(i);
+ }
+
+ /**
+ * Send intent to host application. Adds host application package name and
+ * our package name.
+ *
+ * @param intent The intent to send.
+ */
+ private void sendToHostApp(final Intent intent) {
+ intent.putExtra(Control.Intents.EXTRA_AEA_PACKAGE_NAME, mContext.getPackageName());
+ intent.setPackage(mHostAppPackageName);
+ mContext.sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);
+ }
+
+ /**
+ * Provides a thread which can read from the socket
+ */
+ private class ServerThread extends Thread {
+ private final Handler mHandler;
+
+ /**
+ * Creates a thread which can read from the socket
+ *
+ * @param handler The handler to post messages on.
+ */
+ public ServerThread(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void run() {
+ try {
+ DataInputStream inStream = new DataInputStream(mLocalServerSocket.accept()
+ .getInputStream());
+ while (!isInterrupted()) {
+ AccessorySensorEvent event = decodeSensorData(inStream);
+ if (event != null) {
+ Message msg = new Message();
+ msg.obj = event;
+ mHandler.sendMessage(msg);
+ }
+ }
+ } catch (IOException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w(e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Decodes data from the socket
+ *
+ * @param inStream The data stream
+ * @return The sensor event.
+ */
+ private AccessorySensorEvent decodeSensorData(DataInputStream inStream) throws IOException {
+ int totalLength = inStream.readInt();
+ if (totalLength == 0) {
+ return null;
+ }
+ int accuracy = inStream.readInt();
+ long timestamp = inStream.readLong();
+ int sensorValueCount = inStream.readInt();
+ float[] sensorValues = new float[sensorValueCount];
+ for (int i = 0; i < sensorValueCount; i++) {
+ sensorValues[i] = inStream.readFloat();
+ }
+ return new AccessorySensorEvent(accuracy, timestamp, sensorValues);
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEvent.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEvent.java
new file mode 100644
index 0000000..a1030e3
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEvent.java
@@ -0,0 +1,81 @@
+/*
+ Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+/**
+ * A sensor event from an accessory.
+ */
+public class AccessorySensorEvent {
+ private final int mAccuracy;
+
+ private final long mTimestamp;
+
+ private final float[] mVal;
+
+ /**
+ * Create a sensor event.
+ *
+ * @param accuracy The accuracy as defined in
+ * {@link com.sonyericsson.extras.liveware.aef.sensor.Sensor.SensorAccuracy}
+ * @param timeStamp The time in nanoseconds when the event fired.
+ * @param sensorValues Array of sensor values. The length of the array is
+ * dependent on the sensor.
+ */
+ public AccessorySensorEvent(int accuracy, long timestamp, float[] sensorValues) {
+ mAccuracy = accuracy;
+ mTimestamp = timestamp;
+ mVal = sensorValues;
+ }
+
+ /**
+ * Get data values from sensor The length of the array is dependent on the
+ * sensor.
+ */
+ public float[] getSensorValues() {
+ return mVal;
+ }
+
+ /**
+ * @return The time in nanoseconds when the event fired.
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * @return The accuracy as defined in
+ * {@link com.sonyericsson.extras.liveware.aef.sensor.Sensor.SensorAccuracy}
+ */
+ public int getAccuracy() {
+ return mAccuracy;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEventListener.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEventListener.java
new file mode 100644
index 0000000..248388f
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorEventListener.java
@@ -0,0 +1,45 @@
+/*
+ Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+/**
+ * The sensor event listener is used to listen for sensor events.
+ */
+public interface AccessorySensorEventListener {
+
+ /**
+ * Called when a new sensor event is received.
+ *
+ * @param accessorySensorEvent The received sensor event.
+ */
+ public void onSensorEvent(AccessorySensorEvent accessorySensorEvent);
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorException.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorException.java
new file mode 100644
index 0000000..cfca1d6
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorException.java
@@ -0,0 +1,41 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+public class AccessorySensorException extends Exception {
+
+ public AccessorySensorException(String string) {
+ super(string);
+ }
+
+ private static final long serialVersionUID = 1L;
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorManager.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorManager.java
new file mode 100644
index 0000000..f653878
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorManager.java
@@ -0,0 +1,306 @@
+/*
+ Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+ Copyright (c) 2011-2013, Sony Mobile Communications AB
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.text.TextUtils;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration.Device;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.DeviceColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostApp;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.HostAppColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorColumns;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorType;
+import com.sonyericsson.extras.liveware.aef.registration.Registration.SensorTypeColumns;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+
+import java.util.ArrayList;
+
+/**
+ * Manages sensors on an accessory.
+ */
+public class AccessorySensorManager {
+
+ public static final int INVALID_ID = -1;
+
+ private final Context mContext;
+
+ private final String mHostAppPackageName;
+
+ /**
+ * Create sensor manager for a host application.
+ *
+ * @param context The context.
+ * @param hostAppPackageName The host application package name.
+ */
+ public AccessorySensorManager(final Context context, final String hostAppPackageName) {
+ mContext = context;
+ mHostAppPackageName = hostAppPackageName;
+ }
+
+ /**
+ * Get sensor.
+ *
+ * @param sensorType The string identifying the sensor type.
+ * @return The sensor or null if no sensor found for the given value
+ */
+ public AccessorySensor getSensor(final String sensorType) {
+ return getSensorForType(sensorType, null);
+ }
+
+ /**
+ * Get sensor with a certain delicate class.
+ *
+ * @param sensorType The string identifying the sensor type.
+ * @param delicate True if delicate, false otherwise.
+ * @return The sensor or null if no sensor found for the given value
+ */
+ public AccessorySensor getSensor(final String sensorType, final boolean delicate) {
+ return getSensorForType(sensorType, delicate);
+ }
+
+ /**
+ * Get the sensor for a specific sensorType.
+ *
+ * @param sensorType The string identifying the sensor type.
+ * @param delicate True if delicate only, false if not delicate, null if
+ * don't care.
+ *
+ * @return The sensor.
+ */
+ private AccessorySensor getSensorForType(final String sensorType, final Boolean delicate) {
+ AccessorySensorType type = getSensorType(sensorType, delicate);
+ if (type == null) {
+ return null;
+ }
+
+ long hostAppId = getHostAppId();
+ if (hostAppId == INVALID_ID) {
+ return null;
+ }
+
+ long deviceId = getDeviceId(hostAppId);
+ if (deviceId == INVALID_ID) {
+ return null;
+ }
+
+ Cursor cursor = null;
+ AccessorySensor sensor = null;
+
+ try {
+ cursor = mContext.getContentResolver().query(
+ com.sonyericsson.extras.liveware.aef.registration.Registration.Sensor.URI,
+ null,
+ SensorColumns.SENSOR_TYPE_ID + "= ? AND " + SensorColumns.DEVICE_ID + " = ?",
+ new String[] {
+ Integer.toString(type.getId()), Long.toString(deviceId)
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ int id = cursor.getInt(cursor.getColumnIndexOrThrow(SensorColumns.SENSOR_ID));
+ boolean isInterruptSupported = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.SUPPORTS_SENSOR_INTERRUPT)) == 1;
+ String name = cursor.getString(cursor.getColumnIndexOrThrow(SensorColumns.NAME));
+ int resolution = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.RESOLUTION));
+ int minimumDelay = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.MINIMUM_DELAY));
+ int maximumRange = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorColumns.MAXIMUM_RANGE));
+
+ sensor = new AccessorySensor(mContext, mHostAppPackageName, id, type,
+ isInterruptSupported, name, resolution, minimumDelay, maximumRange);
+
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return sensor;
+ }
+
+ /**
+ * Get sensor type.
+ *
+ * @param sensorType The string identifying the sensor type.
+ * @param delicate True if delicate only, false if not delicate, null if
+ * don't care.
+ * @return The sensor type.
+ */
+ private AccessorySensorType getSensorType(final String sensorType, final Boolean delicate) {
+ Cursor cursor = null;
+ AccessorySensorType type = null;
+
+ StringBuilder builder = new StringBuilder();
+ ArrayList arguments = new ArrayList();
+
+ if (sensorType != null && !TextUtils.isEmpty(sensorType)) {
+ builder.append(SensorTypeColumns.TYPE + " = ?");
+ arguments.add(sensorType);
+ }
+
+ if (delicate != null) {
+ if (builder.length() > 0) {
+ builder.append(" AND ");
+ }
+
+ builder.append(SensorTypeColumns.DELICATE_SENSOR_DATA + " = ?");
+ arguments.add(delicate ? "1" : "0");
+ }
+
+ String where = builder.toString();
+ try {
+ cursor = mContext.getContentResolver().query(SensorType.URI, new String[] {
+ SensorTypeColumns._ID, SensorTypeColumns.DELICATE_SENSOR_DATA
+ }, where, arguments.toArray(new String[arguments.size()]), null);
+ if (cursor != null && cursor.moveToFirst()) {
+ boolean isDelicate = cursor.getInt(cursor
+ .getColumnIndexOrThrow(SensorTypeColumns.DELICATE_SENSOR_DATA)) == 1;
+ int id = cursor.getInt(cursor.getColumnIndexOrThrow(SensorTypeColumns._ID));
+ type = new AccessorySensorType(sensorType, isDelicate, id);
+ }
+ if (type == null) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query SensorType");
+ }
+ return null;
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor types", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor types", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query sensor types", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return type;
+ }
+
+ /**
+ * Get host application id.
+ *
+ * @return The host application id.
+ */
+ private long getHostAppId() {
+ Cursor cursor = null;
+ long hostAppId = INVALID_ID;
+ try {
+ cursor = mContext.getContentResolver().query(HostApp.URI, null,
+ HostAppColumns.PACKAGE_NAME + " = ?", new String[] {
+ mHostAppPackageName
+ }, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ hostAppId = cursor.getLong(cursor.getColumnIndexOrThrow(HostAppColumns._ID));
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query host apps", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return hostAppId;
+ }
+
+ /**
+ * Get device id. This assumes only one device per host application.
+ *
+ * @param hostAppId The host application id.
+ *
+ * @return The device id.
+ */
+ private long getDeviceId(final long hostAppId) {
+ Cursor cursor = null;
+ long deviceId = INVALID_ID;
+ try {
+ cursor = mContext.getContentResolver().query(Device.URI, null,
+ DeviceColumns.HOST_APPLICATION_ID + " = " + hostAppId, null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ deviceId = cursor.getLong(cursor.getColumnIndexOrThrow(DeviceColumns._ID));
+ }
+ } catch (SQLException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to get device id", exception);
+ }
+ } catch (SecurityException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to get device id", exception);
+ }
+ } catch (IllegalArgumentException exception) {
+ if (Dbg.DEBUG) {
+ Dbg.e("Failed to get device id", exception);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return deviceId;
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorType.java b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorType.java
new file mode 100644
index 0000000..a448438
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/sensor/AccessorySensorType.java
@@ -0,0 +1,87 @@
+/*
+ Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.sensor;
+
+/**
+ * The accessory sensor type represents a sensor type.
+ */
+public class AccessorySensorType {
+
+ private final String mName;
+
+ private final boolean mIsDelicate;
+
+ private final int mId;
+
+ /**
+ * Create accessory sensor type.
+ *
+ * @param name The name.
+ * @param isDelicate True if sensor data is delicate.
+ * @param id The sensor type id.
+ */
+ public AccessorySensorType(final String name, final boolean isDelicate, final int id) {
+ mName = name;
+ mIsDelicate = isDelicate;
+ mId = id;
+ }
+
+ /**
+ * Get the sensor type name.
+ *
+ * @see Registration.SensorTypeColumns.#TYPE
+ * @return
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Is the sensor type delicate.
+ *
+ * @see Registration.SensorTypeColumns.#DELICATE_SENSOR_DATA
+ * @return True if delicate.
+ */
+ public boolean isDelicate() {
+ return mIsDelicate;
+ }
+
+ /**
+ * Get the type id.
+ *
+ * @return The type id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetEvent.java b/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetEvent.java
new file mode 100644
index 0000000..9f58cc5
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetEvent.java
@@ -0,0 +1,304 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.widget;
+
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.text.TextUtils;
+
+/**
+ * The notification widget event class represents an event in the notification
+ * database.
+ */
+public class NotificationWidgetEvent {
+ protected String mName = null;
+
+ protected String mTitle = null;
+
+ protected String mMessage = null;
+
+ protected long mTime = 0L;
+
+ protected int mCount = 0;
+
+ protected long mSourceId = 0;
+
+ protected String mContactReference = null;
+
+ protected String mProfileImageUri = null;
+
+ protected String mFriendKey = null;
+
+ protected final Context mContext;
+
+ /**
+ * Create notification widget event.
+ *
+ * @param context The context.
+ */
+ public NotificationWidgetEvent(final Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == null) {
+ return false;
+ }
+ if (!(object instanceof NotificationWidgetEvent)) {
+ return false;
+ }
+
+ NotificationWidgetEvent event = (NotificationWidgetEvent)object;
+ if (!TextUtils.equals(mName, event.mName)) {
+ return false;
+ }
+ if (!TextUtils.equals(mTitle, event.mTitle)) {
+ return false;
+ }
+ if (!TextUtils.equals(mMessage, event.mMessage)) {
+ return false;
+ }
+ if (mTime != event.mTime) {
+ return false;
+ }
+ if (mCount != event.mCount) {
+ return false;
+ }
+ if (mSourceId != event.mSourceId) {
+ return false;
+ }
+ if (!TextUtils.equals(mContactReference, event.mContactReference)) {
+ return false;
+ }
+ if (!TextUtils.equals(mProfileImageUri, event.mProfileImageUri)) {
+ return false;
+ }
+ if (!TextUtils.equals(mFriendKey, event.mFriendKey)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ // Hashing not supported.
+ assert false : "hashCode not designed";
+ return -1;
+ }
+
+
+ /**
+ * Get the time.
+ *
+ * @return The time.
+ */
+ public long getTime() {
+ return mTime;
+ }
+
+ /**
+ * Set the time.
+ *
+ * @param time The time.
+ */
+ public void setTime(long time) {
+ mTime = time;
+ }
+
+ /**
+ * Get the count.
+ *
+ * @return The count.
+ */
+ public int getCount() {
+ return mCount;
+ }
+
+ /**
+ * Set the count.
+ *
+ * @param count The count.
+ */
+ public void setCount(int count) {
+ mCount = count;
+ }
+
+ /**
+ * Get the source id.
+ *
+ * @return The source id.
+ */
+ public long getSourceId() {
+ return mSourceId;
+ }
+
+ /**
+ * Set the source id.
+ *
+ * @param sourceId The source id.
+ */
+ public void setSourceId(long sourceId) {
+ mSourceId = sourceId;
+ }
+
+ /**
+ * Set the name.
+ *
+ * @param name The name.
+ */
+ public void setName(String name) {
+ mName = name;
+ }
+
+ /**
+ * Set the title.
+ *
+ * @param title The title.
+ */
+ public void setTitle(String title) {
+ mTitle = title;
+ }
+
+ /**
+ * Set the message.
+ *
+ * @param message The message.
+ */
+ public void setMessage(String message) {
+ mMessage = message;
+ }
+
+ /**
+ * Set the contact reference.
+ *
+ * @param contactReference The contact reference.
+ */
+ public void setContactReference(String contactReference) {
+ mContactReference = contactReference;
+ }
+
+ /**
+ * Set the profile image URI.
+ *
+ * @param profileImageUri The profile image URI.
+ */
+ public void setProfileImageUri(String profileImageUri) {
+ mProfileImageUri = profileImageUri;
+ }
+
+ /**
+ * Get the widget image.
+ *
+ * @return The image.
+ */
+ public Bitmap getImage() {
+ // If profile image explicitly set then use it.
+ // Otherwise get the contact photo.
+ if (mProfileImageUri != null) {
+ return ExtensionUtils.getBitmapFromUri(mContext, mProfileImageUri);
+ } else {
+ if (mContactReference != null) {
+ Uri uri = Uri.parse(mContactReference);
+ return ExtensionUtils.getContactPhoto(mContext, uri);
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.e("No image available");
+ }
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Get the name.
+ *
+ * @return The name.
+ */
+ public String getName() {
+ // If display name explicitly set then use it.
+ // Otherwise get the display name from the contact.
+ if (mName != null) {
+ return mName;
+ } else {
+ if (mContactReference != null) {
+ Uri uri = Uri.parse(mContactReference);
+ return ExtensionUtils.getContactName(mContext, uri);
+ } else {
+ if (Dbg.DEBUG) {
+ Dbg.e("No name");
+ }
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Get the title.
+ *
+ * @return The title.
+ */
+ public String getTitle() {
+ // If title exist then use it otherwise use message.
+ if (mTitle != null) {
+ return mTitle;
+ } else {
+ return mMessage;
+ }
+ }
+
+ /**
+ * Get the friend key.
+ *
+ * @return The friend key.
+ */
+ public String getFriendKey() {
+ return mFriendKey;
+ }
+
+ /**
+ * Set the friend key.
+ *
+ * @param friendKey The friend key.
+ */
+ public void setFriendKey(String friendKey) {
+ mFriendKey = friendKey;
+ }
+
+
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetExtension.java b/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetExtension.java
new file mode 100644
index 0000000..1bb6dd4
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/widget/NotificationWidgetExtension.java
@@ -0,0 +1,473 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.widget;
+
+import java.util.GregorianCalendar;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.text.format.DateUtils;
+
+import com.dattasmoon.pebble.plugin.R;
+import com.sonyericsson.extras.liveware.aef.notification.Notification;
+import com.sonyericsson.extras.liveware.aef.widget.Widget;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.SmartWatchConst;
+import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;
+
+/**
+ * The widget extension handles a widget on an accessory.
+ */
+public class NotificationWidgetExtension extends WidgetExtension {
+
+ public static final int REQUIRED_API_VERSION = 1;
+
+ protected static final String[] EVENT_PROJECTION = {
+ Notification.EventColumns.CONTACTS_REFERENCE,
+ Notification.EventColumns.DISPLAY_NAME,
+ Notification.EventColumns.FRIEND_KEY,
+ Notification.EventColumns.TITLE, Notification.EventColumns.MESSAGE,
+ Notification.EventColumns.PROFILE_IMAGE_URI,
+ Notification.EventColumns.PUBLISHED_TIME,
+ Notification.EventColumns.SOURCE_ID };
+
+ protected final int mNoEventsTextResourceId;
+
+ protected final int mDefaultSourceIconResourceId;
+
+ protected final Handler mHandler;
+
+ protected final String mExtensionKey;
+
+ private EventContentObserver mEventContentObserver = null;
+
+ protected NotificationWidgetEvent mLastEvent = null;
+
+ /**
+ * Create notification extension widget.
+ *
+ * @param context
+ * The context.
+ * @param handler
+ * The handler.
+ * @param hostAppPackageName
+ * The host app package name for this widget.
+ * @param extensionKey
+ * The extension key.
+ * @param noEventsTextResourceid
+ * The resource id of the string to show in the no events widget.
+ * @param defaultSourceIconResourceId
+ * The resource if of the image to show when there are no events
+ */
+ public NotificationWidgetExtension(Context context, Handler handler,
+ String hostAppPackageName, String extensionKey,
+ int noEventsTextResourceid, int defaultSourceIconResourceId) {
+ super(context, hostAppPackageName);
+ if (extensionKey == null) {
+ throw new IllegalArgumentException("extensionKey == null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler == null");
+ }
+
+ mHandler = handler;
+
+ mNoEventsTextResourceId = noEventsTextResourceid;
+ mDefaultSourceIconResourceId = defaultSourceIconResourceId;
+
+ mExtensionKey = extensionKey;
+
+ }
+
+ /**
+ * Get supported widget width.
+ *
+ * @param context
+ * The context.
+ * @return the width.
+ */
+ public static int getSupportedWidgetWidth(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_width_outer);
+ }
+
+ /**
+ * Get supported widget height.
+ *
+ * @param context
+ * The context.
+ * @return the height.
+ */
+ public static int getSupportedWidgetHeight(Context context) {
+ return context.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_height_outer);
+ }
+
+ @Override
+ public void onScheduledRefresh() {
+ // Time stored in event is absolute time so we must force update of the
+ // display when it is time for a scheduled refresh.
+ updateWidget(false);
+ }
+
+ /**
+ * Start refreshing the widget. The widget is now visible.
+ */
+ @Override
+ public void onStartRefresh() {
+ // Start observing the event table to get notified when new events
+ // arrive.
+ mEventContentObserver = new EventContentObserver(mHandler);
+ mContext.getContentResolver().registerContentObserver(
+ Notification.Event.URI, true, mEventContentObserver);
+
+ // Widget just started. Update image.
+ updateWidget(false);
+ }
+
+ /**
+ * Stop refreshing the widget. The widget is no longer visible.
+ */
+ @Override
+ public void onStopRefresh() {
+ cancelScheduledRefresh(mExtensionKey);
+
+ // Stop observing the event table.
+ if (mEventContentObserver != null) {
+ mContext.getContentResolver().unregisterContentObserver(
+ mEventContentObserver);
+ mEventContentObserver = null;
+ }
+ }
+
+ /**
+ * The widget has been touched.
+ *
+ * @param type
+ * The type of touch event.
+ * @param x
+ * The x position of the touch event.
+ * @param y
+ * The y position of the touch event.
+ */
+ @Override
+ public void onTouch(final int type, final int x, final int y) {
+ if (!SmartWatchConst.ACTIVE_WIDGET_TOUCH_AREA.contains(x, y)) {
+ if (Dbg.DEBUG) {
+ Dbg.d("Touch outside active area x: " + x + " y: " + y);
+ }
+ return;
+ }
+
+ // Both short and long tap enters next level.
+ // Enter next level.
+ Intent intent = new Intent(
+ Widget.Intents.WIDGET_ENTER_NEXT_LEVEL_INTENT);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Get bitmap.
+ *
+ * @param event
+ * The event or null if no events.
+ *
+ * @return the bitmap to send to the host application.
+ */
+ public Bitmap getBitmap(final NotificationWidgetEvent event) {
+ SmartWatchWidgetImage widgetImage;
+ if (null == event) {
+ widgetImage = new SmartWatchWidgetImage(mContext);
+ widgetImage.setText(getNoEventsText());
+ widgetImage.setIconByResourceId(mDefaultSourceIconResourceId);
+ } else {
+ widgetImage = new SmartWatchNotificationWidgetImage(mContext, event);
+ widgetImage.setIconByUri(getSourceIconUri(event.getSourceId()));
+ cancelScheduledRefresh(mExtensionKey);
+ if (Math.abs(event.getTime() - System.currentTimeMillis()) < (DateUtils.HOUR_IN_MILLIS + DateUtils.MINUTE_IN_MILLIS)) {
+ // refresh when next minute starts
+ GregorianCalendar gregorianCalendar = new GregorianCalendar();
+ gregorianCalendar.add(GregorianCalendar.SECOND, 1);
+ gregorianCalendar.add(GregorianCalendar.MINUTE, 1);
+ gregorianCalendar.set(GregorianCalendar.SECOND, 0);
+ scheduleRefresh(gregorianCalendar.getTimeInMillis(),
+ mExtensionKey);
+ }
+ }
+ return widgetImage.getBitmap();
+ }
+
+ /**
+ * Update widget.
+ *
+ * @param checkEvent
+ * True if we shall check if the event is new before we update.
+ * False to disable check.
+ */
+ protected void updateWidget(boolean checkEvent) {
+ if (Dbg.DEBUG) {
+ Dbg.d("updateWidget");
+ }
+
+ NotificationWidgetEvent event = getEvent();
+
+ if (checkEvent) {
+ // Check if the info is the same as the one already shown.
+ if (mLastEvent != null && mLastEvent.equals(event)
+ || mLastEvent == null && event == null) {
+ if (Dbg.DEBUG) {
+ Dbg.d("No change in widget data. No update.");
+ }
+ return;
+ }
+ }
+ mLastEvent = event;
+
+ showBitmap(getBitmap(event));
+ }
+
+ /**
+ * Get the widget event to show.
+ *
+ * @return The widget event to show.
+ */
+ protected NotificationWidgetEvent getEvent() {
+ Cursor cursor = null;
+ NotificationWidgetEvent event = null;
+
+ try {
+ cursor = getEventCursor();
+ if (cursor == null) {
+ return null;
+ }
+
+ event = new NotificationWidgetEvent(mContext);
+
+ // Get the contact Uri
+ event.setContactReference(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.CONTACTS_REFERENCE)));
+
+ // Display name
+ event.setName(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.DISPLAY_NAME)));
+
+ // Contact picture
+ event.setProfileImageUri(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.PROFILE_IMAGE_URI)));
+
+ // Title
+ event.setTitle(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.TITLE)));
+
+ // Message
+ event.setMessage(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.MESSAGE)));
+
+ // Time
+ event.setTime(cursor.getLong(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.PUBLISHED_TIME)));
+
+ // Count
+ event.setCount(getCount());
+
+ // Source id
+ event.setSourceId(cursor.getLong(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.SOURCE_ID)));
+
+ // Friend key
+ event.setFriendKey(cursor.getString(cursor
+ .getColumnIndexOrThrow(Notification.EventColumns.FRIEND_KEY)));
+
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return event;
+ }
+
+ /**
+ * Get the event cursor for most relevant event, defined as: the event with
+ * the lowest PUBLISHED_TIME with PUBLISHED_TIME > current time. If no such
+ * event choose the event with highest PUBLISHED_TIME.
+ *
+ * @return The event cursor for the event
+ */
+ protected Cursor getEventCursor() {
+ Cursor cursor = null;
+ long now = System.currentTimeMillis();
+
+ cursor = NotificationUtil.queryEventsFromEnabledSources(mContext,
+ EVENT_PROJECTION, Notification.EventColumns.PUBLISHED_TIME
+ + ">" + now, null,
+ Notification.EventColumns.PUBLISHED_TIME + " asc limit 1");
+
+ if (cursor == null || !cursor.moveToFirst()) {
+ if (cursor != null) {
+ cursor.close();
+ }
+ cursor = NotificationUtil.queryEventsFromEnabledSources(mContext,
+ EVENT_PROJECTION, null, null,
+ Notification.EventColumns.PUBLISHED_TIME + " desc limit 1");
+ if (cursor == null) {
+ return null;
+ }
+ if (!cursor.moveToFirst()) {
+ cursor.close();
+ return null;
+ }
+
+ }
+
+ return cursor;
+ }
+
+ /**
+ * Get the number of new events.
+ *
+ * @return The number of new events.
+ */
+ protected int getCount() {
+ int count = 0;
+ Cursor cursor = null;
+ try {
+ cursor = NotificationUtil.queryEventsFromEnabledSources(mContext,
+ null, Notification.EventColumns.EVENT_READ_STATUS + "= 0",
+ null, null);
+ if (cursor != null) {
+ count = cursor.getCount();
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query events", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * Get the source icon uri for a source id.
+ *
+ * @param sourceId
+ * The source id.
+ *
+ * @return The source icon uri.
+ */
+ protected String getSourceIconUri(long sourceId) {
+ String iconString = null;
+ Cursor cursor = null;
+ try {
+ cursor = NotificationUtil
+ .querySources(mContext, null,
+ Notification.SourceColumns._ID + "=" + sourceId,
+ null, null);
+ if (cursor != null && cursor.moveToFirst()) {
+ iconString = cursor
+ .getString(cursor
+ .getColumnIndexOrThrow(Notification.SourceColumns.ICON_URI_1));
+ }
+ } catch (SQLException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } catch (SecurityException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } catch (IllegalArgumentException e) {
+ if (Dbg.DEBUG) {
+ Dbg.w("Failed to query source", e);
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return iconString;
+ }
+
+ /**
+ * Get the text to show in the no events widget.
+ *
+ * @return The text to show in the no events widget.
+ */
+ protected String getNoEventsText() {
+ return mContext.getString(mNoEventsTextResourceId);
+ }
+
+ /**
+ * The event content observer observes the event table in the notification
+ * database.
+ */
+ private class EventContentObserver extends ContentObserver {
+ public EventContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ // Update widget if the event has been changed.
+ updateWidget(true);
+ }
+ }
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchNotificationWidgetImage.java b/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchNotificationWidgetImage.java
new file mode 100644
index 0000000..bc72815
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchNotificationWidgetImage.java
@@ -0,0 +1,98 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.dattasmoon.pebble.plugin.R;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+
+/**
+ * The class decorates a widget image with notification specific UI components.
+ */
+public class SmartWatchNotificationWidgetImage extends SmartWatchWidgetImage {
+
+ private final NotificationWidgetEvent mEvent;
+
+ /**
+ * Create notification widget image.
+ *
+ * @param context
+ * The context.
+ * @param event
+ * The event.
+ */
+ public SmartWatchNotificationWidgetImage(final Context context,
+ final NotificationWidgetEvent event) {
+ super(context);
+ setInnerLayoutResourceId(R.layout.smart_watch_notification_widget);
+ setBadgeCount(event.getCount());
+ mEvent = event;
+ }
+
+ @Override
+ protected void applyInnerLayout(LinearLayout innerLayout) {
+
+ Bitmap backgroundBitmap = mEvent.getImage();
+ if (null != backgroundBitmap) {
+ ((ImageView) innerLayout
+ .findViewById(R.id.smart_watch_notification_widget_background))
+ .setImageBitmap(backgroundBitmap);
+
+ ((ImageView) innerLayout
+ .findViewById(R.id.smart_watch_notification_widget_text_background))
+ .setVisibility(View.VISIBLE);
+ }
+
+ // set title
+ ((TextView) innerLayout
+ .findViewById(R.id.smart_watch_notification_widget_text_title))
+ .setText(mEvent.getTitle());
+
+ // set time stamp
+ String time = ExtensionUtils.getFormattedTime(mEvent.getTime());
+ ((TextView) innerLayout
+ .findViewById(R.id.smart_watch_notification_widget_text_time))
+ .setText(time);
+
+ // set name
+ ((TextView) innerLayout
+ .findViewById(R.id.smart_watch_notification_widget_text_name))
+ .setText(mEvent.getName());
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchWidgetImage.java b/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchWidgetImage.java
new file mode 100644
index 0000000..6448e30
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/widget/SmartWatchWidgetImage.java
@@ -0,0 +1,307 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.widget;
+
+import java.io.IOException;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.dattasmoon.pebble.plugin.R;
+
+/**
+ * The SmartWatchWidgetImage class is used to generate widget bitmap which
+ * follows official SmartWatch layout of where e.g. frame and icon shall be
+ * located. The layout inside the frame can be customized by applying setting
+ * inner layout resource id.
+ */
+public class SmartWatchWidgetImage {
+
+ private final Bitmap mBitmap;
+
+ private Bitmap mIconBitmap;
+
+ private final Canvas mCanvas;
+
+ private String mText;
+
+ private int mBadgeCount;
+
+ private int mInnerLayoutResid;
+
+ protected final Context mContext;
+
+ protected final BitmapFactory.Options mBitmapOptions;
+
+ protected final int mOuterWidth;
+
+ protected final int mOuterHeight;
+
+ protected final int mInnerWidth;
+
+ protected final int mInnerHeight;
+
+ /**
+ * Initiate the SmartWatch widget image.
+ *
+ * @param context
+ * The context.
+ */
+ public SmartWatchWidgetImage(final Context context) {
+ mContext = context;
+
+ mText = null;
+ mIconBitmap = null;
+ mInnerLayoutResid = 0;
+ mBadgeCount = 0;
+
+ mOuterWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_width_outer);
+ mOuterHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_height_outer);
+
+ mInnerWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_width_inner);
+ mInnerHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.smart_watch_widget_height_inner);
+
+ mBitmap = Bitmap.createBitmap(mOuterWidth, mOuterHeight,
+ Bitmap.Config.ARGB_8888);
+
+ // Set the density to default to avoid scaling.
+ mBitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+ mCanvas = new Canvas(mBitmap);
+
+ // Options to avoid scaling.
+ mBitmapOptions = new BitmapFactory.Options();
+ mBitmapOptions.inDensity = DisplayMetrics.DENSITY_DEFAULT;
+ mBitmapOptions.inTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
+ mBitmapOptions.inScaled = false;
+ }
+
+ /**
+ * Set custom text. Typically used when only a text shall be displayed, and
+ * no specific layout is needed.
+ *
+ * @param text
+ * The text.
+ *
+ * @return this.
+ */
+ public SmartWatchWidgetImage setText(String text) {
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Set widget icon by id.
+ *
+ * @param iconId
+ * The icon id.
+ *
+ * @return this.
+ */
+ public SmartWatchWidgetImage setIconByResourceId(int iconId) {
+ mIconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
+ iconId, mBitmapOptions);
+ return this;
+ }
+
+ /**
+ * Set widget icon by uri.
+ *
+ * @param iconUri
+ * The icon uri.
+ *
+ * @return this.
+ */
+ public SmartWatchWidgetImage setIconByUri(String iconUri) {
+ if (iconUri == null) {
+ return this;
+ }
+
+ Uri uri = Uri.parse(iconUri);
+ if (uri != null) {
+ try {
+ mIconBitmap = MediaStore.Images.Media.getBitmap(
+ mContext.getContentResolver(), uri);
+ // We use default density for all bitmaps to avoid scaling.
+ mIconBitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+ } catch (IOException e) {
+
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Set custom layout inside the widget frame. Not needed setText is used.
+ *
+ * @param layoutId
+ * The layout id.
+ *
+ * @return this.
+ */
+ public SmartWatchWidgetImage setInnerLayoutResourceId(int layoutId) {
+ mInnerLayoutResid = layoutId;
+ return this;
+ }
+
+ /**
+ * Set number to be shown in upper left badge. Badge is not visible if
+ * number < 1.
+ *
+ * @param number
+ * The number.
+ *
+ * @return this.
+ */
+ public SmartWatchWidgetImage setBadgeCount(int number) {
+ mBadgeCount = number;
+ return this;
+ }
+
+ /**
+ * Apply Set custom text. Typically used when only a text shall be
+ * displayed, and no specific layout is needed.
+ *
+ * @param text
+ * The text.
+ *
+ * @return this.
+ */
+ private void draw() {
+ LinearLayout root = new LinearLayout(mContext);
+ root.setLayoutParams(new LayoutParams(mOuterWidth, mOuterHeight));
+
+ LinearLayout linearLayout = (LinearLayout) LinearLayout.inflate(
+ mContext, R.layout.smart_watch_widget, root);
+
+ if (mBadgeCount > 0) {
+ TextView badgeText = (TextView) linearLayout
+ .findViewById(R.id.smart_watch_widget_event_counter_text);
+ badgeText.setText(Integer.toString(mBadgeCount));
+ badgeText.setVisibility(View.VISIBLE);
+
+ ImageView badgeBackground = (ImageView) linearLayout
+ .findViewById(R.id.smart_watch_widget_event_counter_badge);
+ badgeBackground.setVisibility(View.VISIBLE);
+ }
+
+ ImageView icon = (ImageView) linearLayout
+ .findViewById(R.id.smart_watch_widget_icon);
+ icon.setImageBitmap(mIconBitmap);
+
+ if (null != mText) {
+ TextView textView = (TextView) linearLayout
+ .findViewById(R.id.smart_watch_widget_custom_text_view);
+ textView.setText(mText);
+ }
+
+ ImageView customImage = (ImageView) linearLayout
+ .findViewById(R.id.smart_watch_widget_custom_image);
+ customImage.setImageBitmap(getInnerBitmap());
+
+ linearLayout.measure(mOuterWidth, mOuterHeight);
+ linearLayout.layout(0, 0, linearLayout.getMeasuredWidth(),
+ linearLayout.getMeasuredHeight());
+
+ linearLayout.draw(mCanvas);
+ }
+
+ /**
+ * Get bitmap inside the frame.
+ *
+ * @return a bitmap or null if no inner layout is applied.
+ */
+ private Bitmap getInnerBitmap() {
+ if (mInnerLayoutResid != 0) {
+ Bitmap innerBitmap = Bitmap.createBitmap(mInnerWidth, mInnerHeight,
+ Bitmap.Config.ARGB_8888);
+
+ // Set the density to default to avoid scaling.
+ innerBitmap.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+
+ LinearLayout root = new LinearLayout(mContext);
+ root.setLayoutParams(new LayoutParams(mInnerWidth, mInnerHeight));
+
+ LinearLayout innerLayout = (LinearLayout) LinearLayout.inflate(
+ mContext, mInnerLayoutResid, root);
+
+ applyInnerLayout(innerLayout);
+
+ innerLayout.measure(mInnerWidth, mInnerHeight);
+ innerLayout.layout(0, 0, innerLayout.getMeasuredWidth(),
+ innerLayout.getMeasuredHeight());
+
+ Canvas innerCanvas = new Canvas(innerBitmap);
+ innerLayout.draw(innerCanvas);
+
+ return innerBitmap;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get bitmap inside the frame.
+ *
+ * Example:
+ * ((TextView)innerLayout.findViewById(R.id.my_custom_widget_city)).
+ * setText("Paris");
+ */
+ protected void applyInnerLayout(LinearLayout innerLayout) {
+ throw new IllegalArgumentException(
+ "applyInnerLayout() not implemented. Child class must override this method since innerLayoutResid != 0 ");
+ }
+
+ /**
+ * Get the bitmap.
+ *
+ * @return The bitmap.
+ */
+ public Bitmap getBitmap() {
+ draw();
+ return mBitmap;
+ }
+
+}
diff --git a/src/com/sonyericsson/extras/liveware/extension/util/widget/WidgetExtension.java b/src/com/sonyericsson/extras/liveware/extension/util/widget/WidgetExtension.java
new file mode 100644
index 0000000..0669f9e
--- /dev/null
+++ b/src/com/sonyericsson/extras/liveware/extension/util/widget/WidgetExtension.java
@@ -0,0 +1,336 @@
+/*
+Copyright (c) 2011, Sony Ericsson Mobile Communications AB
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of the Sony Ericsson Mobile Communications AB nor the names
+ of its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.sonyericsson.extras.liveware.extension.util.widget;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+
+import com.sonyericsson.extras.liveware.aef.registration.Registration;
+import com.sonyericsson.extras.liveware.aef.widget.Widget;
+import com.sonyericsson.extras.liveware.extension.util.Dbg;
+import com.sonyericsson.extras.liveware.extension.util.ExtensionUtils;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * The widget extension handles a widget on an accessory.
+ */
+public abstract class WidgetExtension {
+
+ private boolean mStarted = false;
+
+ protected final Context mContext;
+
+ protected final String mHostAppPackageName;
+
+ public static final String SCHEDULED_REFRESH_INTENT = "com.sonyericsson.extras.liveware.extension.util.widget.scheduled.refresh";
+
+ /**
+ * Create widget extension.
+ *
+ * @param context The context.
+ * @param hostAppPackageName Package name of host application.
+ */
+ public WidgetExtension(Context context, String hostAppPackageName) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ if (hostAppPackageName == null) {
+ throw new IllegalArgumentException("hostAppPackageName == null");
+ }
+ mContext = context;
+ mHostAppPackageName = hostAppPackageName;
+ }
+
+ /**
+ * Start widget refresh.
+ */
+ public final void startRefresh() {
+ mStarted = true;
+ onStartRefresh();
+ }
+
+ /**
+ * Stop widget refresh.
+ */
+ public final void stopRefresh() {
+ mStarted = false;
+ onStopRefresh();
+ }
+
+ /**
+ * Destroy widget.
+ */
+ public final void destroy() {
+ // If started then stop it first.
+ if (mStarted) {
+ stopRefresh();
+ }
+
+ onDestroy();
+ }
+
+ /**
+ * Start refreshing the widget. The widget is now visible.
+ */
+ public abstract void onStartRefresh();
+
+ /**
+ * Stop refreshing the widget. The widget is no longer visible.
+ */
+ public abstract void onStopRefresh();
+
+ /**
+ * Override this method to take action on scheduled refresh. Example of how
+ * to schedule a refresh every 10th second in {@link #onStartRefresh()} and
+ * cancel it in {@link #onStopRefresh()}
+ *
+ *
+ * public void startRefresh() {
+ * // Update now and every 10th second
+ * scheduleRepeatingRefresh(System.currentTimeMillis(), 10 * 1000,
+ * SampleWidgetService.EXTENSION_KEY);
+ * }
+ *
+ * public void stopRefresh() {
+ * cancelScheduledRefresh(SampleWidgetService.EXTENSION_KEY);
+ * }
+ *
+ * public void onScheduledRefresh() {
+ * // Update widget...
+ * }
+ *
+ *
+ * @see #scheduleRefresh(long, String)
+ * @see #scheduleRepeatingRefresh(long, long, String)
+ * @see #cancelScheduledRefresh(String)
+ */
+ public void onScheduledRefresh() {
+
+ }
+
+ /**
+ * Utility that creates the pending intent used to schedule a refresh or to
+ * cancel refreshing
+ *
+ * @see #onScheduledRefresh()
+ * @return The pending intent
+ */
+ private PendingIntent createPendingRefreshIntent(String extensionKey) {
+ Intent intent = new Intent(SCHEDULED_REFRESH_INTENT);
+ intent.putExtra(Widget.Intents.EXTRA_EXTENSION_KEY, extensionKey);
+ intent.putExtra(Widget.Intents.EXTRA_AHA_PACKAGE_NAME, mHostAppPackageName);
+ intent.setPackage(mContext.getPackageName());
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ return pi;
+ }
+
+ /**
+ * Schedule a repeating refresh.
+ *
+ * @see #onScheduledRefresh()
+ * @see #scheduleRefresh(long, String)
+ * @see #cancelScheduledRefresh(String)
+ * @param triggerAtTime Time the scheduled refresh should trigger first time
+ * in {@link System#currentTimeMillis()} time.
+ * @param interval Interval between subsequent repeats of the scheduled
+ * refresh.
+ * @param extensionKey The extension key
+ */
+ protected void scheduleRepeatingRefresh(long triggerAtTime, long interval, String extensionKey) {
+ AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+ am.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, interval,
+ createPendingRefreshIntent(extensionKey));
+ }
+
+ /**
+ * Schedule a refresh.
+ *
+ * @param triggerAtTime Time the scheduled refresh should trigger in
+ * {@link System#currentTimeMillis()} time.
+ * @param extensionKey The extension key
+ */
+ protected void scheduleRefresh(long triggerAtTime, String extensionKey) {
+ AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+ am.set(AlarmManager.RTC_WAKEUP, triggerAtTime,
+ createPendingRefreshIntent(extensionKey));
+ }
+
+ /**
+ * Cancel any pending scheduled refresh associated with the extension key.
+ *
+ * @param extensionKey The extension key
+ */
+ protected void cancelScheduledRefresh(String extensionKey) {
+ AlarmManager mgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mgr.cancel(createPendingRefreshIntent(extensionKey));
+ }
+
+ /**
+ * Take action based on request code
+ *
+ * @see WidgetReceiver#doActionOnAllWidgets(int)
+ * @param requestCode Code used to distinguish between different actions.
+ * @param bundle Optional bundle with additional information.
+ */
+ public void onDoAction(int requestCode, Bundle bundle) {
+
+ }
+
+ /**
+ * Called to notify a widget extension that it is no longer used and is
+ * being removed. The widget extension should clean up any resources it
+ * holds (threads, registered receivers, etc) at this point.
+ */
+ public void onDestroy() {
+
+ }
+
+ /**
+ * The widget has been touched. Override to handle touch events.
+ *
+ * @param type The type of touch event.
+ * @param x The x position of the touch event.
+ * @param y The y position of the touch event.
+ */
+ public void onTouch(final int type, final int x, final int y) {
+
+ }
+
+ /**
+ * Called when an object click event has occurred.
+ *
+ * @param type The type of click event
+ * @param layoutReference The referenced layout object
+ */
+ public void onObjectClick(final int type, final int layoutReference) {
+
+ }
+
+ /**
+ * Sends an image to the host application.
+ *
+ * @param resourceId The image resource id.
+ */
+ protected void sendImageToHostApp(final int resourceId) {
+ Intent intent = new Intent();
+ intent.setAction(Widget.Intents.WIDGET_IMAGE_UPDATE_INTENT);
+ BitmapDrawable bmd = (BitmapDrawable) mContext.getResources().getDrawable(resourceId);
+ ByteArrayOutputStream os = new ByteArrayOutputStream(256);
+ Bitmap bm = bmd.getBitmap();
+ bm.compress(CompressFormat.PNG, 100, os);
+ byte[] buffer = os.toByteArray();
+ intent.putExtra(Widget.Intents.EXTRA_WIDGET_IMAGE_DATA, buffer);
+
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Send intent to host application. Adds host application package name and
+ * our package name.
+ *
+ * @param intent The intent to send.
+ */
+ protected void sendToHostApp(final Intent intent) {
+ intent.putExtra(Widget.Intents.EXTRA_AEA_PACKAGE_NAME, mContext.getPackageName());
+ intent.setPackage(mHostAppPackageName);
+ mContext.sendBroadcast(intent, Registration.HOSTAPP_PERMISSION);
+ }
+
+ /**
+ * Show bitmap on accessory.
+ *
+ * @param bitmap The bitmap to show.
+ */
+ protected void showBitmap(final Bitmap bitmap) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(256);
+ bitmap.compress(CompressFormat.PNG, 100, outputStream);
+
+ Intent intent = new Intent(Widget.Intents.WIDGET_IMAGE_UPDATE_INTENT);
+ intent.putExtra(Widget.Intents.EXTRA_WIDGET_IMAGE_DATA, outputStream.toByteArray());
+
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Show a layout on the accessory.
+ *
+ * @param layoutId The layout resource id.
+ * @param layoutData The layout data.
+ */
+ protected void showLayout(final int layoutId) {
+ Intent intent = new Intent(Widget.Intents.WIDGET_PROCESS_LAYOUT_INTENT);
+ intent.putExtra(Widget.Intents.EXTRA_DATA_XML_LAYOUT, layoutId);
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Update an image in a specific layout, on the accessory.
+ *
+ * @param layoutReference The referenced resource within the current layout.
+ * @param resourceId The image resource id.
+ */
+ protected void sendImage(final int layoutReference, final int resourceId) {
+ if (Dbg.DEBUG) {
+ Dbg.d("sendImage");
+ }
+
+ Intent intent = new Intent(Widget.Intents.WIDGET_SEND_IMAGE_INTENT);
+ intent.putExtra(Widget.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Widget.Intents.EXTRA_WIDGET_IMAGE_URI,
+ ExtensionUtils.getUriString(mContext, resourceId));
+ sendToHostApp(intent);
+ }
+
+ /**
+ * Update a TextView in a specific layout, on the accessory.
+ *
+ * @param layoutReference The referenced resource within the current layout.
+ * @param text The text to be updated.
+ */
+ protected void sendText(final int layoutReference, final String text) {
+ Intent intent = new Intent(Widget.Intents.WIDGET_SEND_TEXT_INTENT);
+ intent.putExtra(Widget.Intents.EXTRA_LAYOUT_REFERENCE, layoutReference);
+ intent.putExtra(Widget.Intents.EXTRA_WIDGET_TEXT, text);
+ sendToHostApp(intent);
+ }
+
+}
diff --git a/src/com/twofortyfouram/locale/BreadCrumber.java b/src/com/twofortyfouram/locale/BreadCrumber.java
new file mode 100644
index 0000000..cdbe8f1
--- /dev/null
+++ b/src/com/twofortyfouram/locale/BreadCrumber.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 two forty four a.m. LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.twofortyfouram.locale;
+
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.dattasmoon.pebble.plugin.R;
+
+/**
+ * Utility class to generate a breadcrumb title string for {@code Activity}
+ * instances in Locale.
+ *
+ * This class cannot be instantiated.
+ */
+public final class BreadCrumber {
+ /**
+ * Static helper method to generate bread crumbs. Bread crumb strings will
+ * be properly formatted for the current language, including right-to-left
+ * languages, as long as the proper
+ * {@link com.twofortyfouram.locale.platform.R.string#twofortyfouram_locale_breadcrumb_format}
+ * string resources have been created.
+ *
+ * @param context
+ * {@code Context} for loading platform resources. Cannot be
+ * null.
+ * @param intent
+ * {@code Intent} to extract the bread crumb from.
+ * @param currentCrumb
+ * The last element of the bread crumb path.
+ * @return {@code String} presentation of the bread crumb. If the intent
+ * parameter is null, then this method returns currentCrumb. If
+ * currentCrumb is null, then this method returns the empty string
+ * "". If intent contains a private Serializable instances as an
+ * extra, then this method returns the empty string "".
+ * @throws IllegalArgumentException
+ * if {@code context} is null.
+ */
+ public static CharSequence generateBreadcrumb(final Context context,
+ final Intent intent, final String currentCrumb) {
+ if (null == context) {
+ throw new IllegalArgumentException("context cannot be null"); //$NON-NLS-1$
+ }
+
+ try {
+ if (null == currentCrumb) {
+ Log.w(Constants.LOG_TAG, "currentCrumb cannot be null"); //$NON-NLS-1$
+ return ""; //$NON-NLS-1$
+ }
+ if (null == intent) {
+ Log.w(Constants.LOG_TAG, "intent cannot be null"); //$NON-NLS-1$
+ return currentCrumb;
+ }
+
+ /*
+ * Note: this is vulnerable to a private serializable attack, but
+ * the try-catch will solve that.
+ */
+ final String breadcrumbString = intent
+ .getStringExtra(com.twofortyfouram.locale.Intent.EXTRA_STRING_BREADCRUMB);
+ if (null != breadcrumbString) {
+ return context
+ .getString(
+ R.string.twofortyfouram_locale_breadcrumb_format,
+ breadcrumbString,
+ context.getString(R.string.twofortyfouram_locale_breadcrumb_separator),
+ currentCrumb);
+ }
+ return currentCrumb;
+ } catch (final Exception e) {
+ Log.e(Constants.LOG_TAG,
+ "Encountered error generating breadcrumb", e); //$NON-NLS-1$
+ return ""; //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Private constructor prevents instantiation.
+ *
+ * @throws UnsupportedOperationException
+ * because this class cannot be instantiated.
+ */
+ private BreadCrumber() {
+ throw new UnsupportedOperationException(
+ "This class is non-instantiable"); //$NON-NLS-1$
+ }
+}
\ No newline at end of file
diff --git a/src/com/twofortyfouram/locale/Constants.java b/src/com/twofortyfouram/locale/Constants.java
new file mode 100644
index 0000000..7e22c51
--- /dev/null
+++ b/src/com/twofortyfouram/locale/Constants.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 two forty four a.m. LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.twofortyfouram.locale;
+
+/**
+ * Utility class containing constants for the Locale Developer Platform.
+ */
+/*
+ * This class is NOT part of the public API.
+ */
+/* package */final class Constants
+{
+ /**
+ * Log tag for logcat messages generated by the Locale Developer Platform
+ */
+ /*
+ * This is NOT a public API. Third party apps should NOT use this log tag for their own log messages.
+ */
+ /* package */static final String LOG_TAG = "LocaleApiLibrary"; //$NON-NLS-1$
+
+ /**
+ * String package name for Locale.
+ */
+ /*
+ * This is NOT a public API. Third parties should NOT rely on this being the only package name for Locale.
+ */
+ /* package */static final String LOCALE_PACKAGE = "com.twofortyfouram.locale"; //$NON-NLS-1$
+
+ /**
+ * Private constructor prevents instantiation
+ *
+ * @throws UnsupportedOperationException because this class cannot be instantiated.
+ */
+ private Constants()
+ {
+ throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
+ }
+}
\ No newline at end of file
diff --git a/src/com/twofortyfouram/locale/Intent.java b/src/com/twofortyfouram/locale/Intent.java
new file mode 100644
index 0000000..7971604
--- /dev/null
+++ b/src/com/twofortyfouram/locale/Intent.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2013 two forty four a.m. LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and limitations under the License.
+ */
+
+package com.twofortyfouram.locale;
+
+import android.os.Parcelable;
+
+/**
+ * Contains Intent constants necessary for interacting with the Locale Developer Platform.
+ */
+public final class Intent
+{
+ /**
+ * Private constructor prevents instantiation.
+ *
+ * @throws UnsupportedOperationException because this class cannot be instantiated.
+ */
+ private Intent()
+ {
+ throw new UnsupportedOperationException("This class is non-instantiable"); //$NON-NLS-1$
+ }
+
+ /**
+ * Ordered broadcast result code indicating that a plug-in condition's state is satisfied (true).
+ *
+ * @see Intent#ACTION_QUERY_CONDITION
+ */
+ public static final int RESULT_CONDITION_SATISFIED = 16;
+
+ /**
+ * Ordered broadcast result code indicating that a plug-in condition's state is not satisfied (false).
+ *
+ * @see Intent#ACTION_QUERY_CONDITION
+ */
+ public static final int RESULT_CONDITION_UNSATISFIED = 17;
+
+ /**
+ * Ordered broadcast result code indicating that a plug-in condition's state is unknown (neither true nor
+ * false).
+ *
+ * If a condition returns UNKNOWN, then Locale will use the last known return value on a best-effort
+ * basis. Best-effort means that Locale may not persist known values forever (e.g. last known values could
+ * hypothetically be cleared after a device reboot or a restart of the Locale process. If
+ * there is no last known return value, then unknown is treated as not satisfied (false).
+ *
+ * The purpose of an UNKNOWN result is to allow a plug-in condition more than 10 seconds to process a
+ * requery. A {@code BroadcastReceiver} must return within 10 seconds, otherwise it will be killed by
+ * Android. A plug-in that needs more than 10 seconds might initially return
+ * {@link #RESULT_CONDITION_UNKNOWN}, subsequently request a requery, and then return either
+ * {@link #RESULT_CONDITION_SATISFIED} or {@link #RESULT_CONDITION_UNSATISFIED}.
+ *
+ * @see Intent#ACTION_QUERY_CONDITION
+ */
+ public static final int RESULT_CONDITION_UNKNOWN = 18;
+
+ /**
+ * {@code Intent} action {@code String} broadcast by Locale to create or edit a plug-in setting. When
+ * Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
+ * plug-in's {@code Activity}. The {@code Intent} may contain a {@link #EXTRA_BUNDLE} that was previously
+ * set by the {@code Activity} result of {@link #ACTION_EDIT_SETTING}.
+ *
+ * There SHOULD be only one {@code Activity} per APK that implements this {@code Intent}. If a single APK
+ * wishes to export multiple plug-ins, it MAY implement multiple Activity instances that implement this
+ * {@code Intent}, however there must only be a single {@link #ACTION_FIRE_SETTING} receiver. In this
+ * scenario, it is the responsibility of the Activities to store enough data in {@link #EXTRA_BUNDLE} to
+ * allow this receiver to disambiguate which "plug-in" is being fired. To avoid user confusion, it is
+ * recommended that only a single plug-in be implemented per APK.
+ *
+ * @see Intent#EXTRA_BUNDLE
+ * @see Intent#EXTRA_STRING_BREADCRUMB
+ */
+ public static final String ACTION_EDIT_SETTING = "com.twofortyfouram.locale.intent.action.EDIT_SETTING"; //$NON-NLS-1$
+
+ /**
+ * {@code Intent} action {@code String} broadcast by Locale to fire a plug-in setting. When Locale
+ * broadcasts this {@code Intent}, it will be sent directly to the package and class of the plug-in's
+ * {@code BroadcastReceiver}. The {@code Intent} will contain a {@link #EXTRA_BUNDLE} that was previously
+ * set by the {@code Activity} result of {@link #ACTION_EDIT_SETTING}.
+ *
+ * There MUST be only one {@code BroadcastReceiver} per APK that implements this {@code Intent}.
+ *
+ * @see Intent#EXTRA_BUNDLE
+ */
+ public static final String ACTION_FIRE_SETTING = "com.twofortyfouram.locale.intent.action.FIRE_SETTING"; //$NON-NLS-1$
+
+ /**
+ * {@code Intent} action {@code String} broadcast by Locale to create or edit a plug-in condition. When
+ * Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
+ * plug-in's {@code Activity}. The {@code Intent} may contain a store-and-forward {@link #EXTRA_BUNDLE}
+ * that was previously set by the {@code Activity} result of {@link #ACTION_EDIT_CONDITION}.
+ *
+ * There SHOULD be only one {@code Activity} per APK that implements this {@code Intent}. If a single APK
+ * wishes to export multiple plug-ins, it MAY implement multiple Activity instances that implement this
+ * {@code Intent}, however there must only be a single {@link #ACTION_QUERY_CONDITION} receiver. In this
+ * scenario, it is the responsibility of the Activities to store enough data in {@link #EXTRA_BUNDLE} to
+ * allow this receiver to disambiguate which "plug-in" is being queried. To avoid user confusion, it is
+ * recommended that only a single plug-in be implemented per APK.
+ *
+ * @see Intent#EXTRA_BUNDLE
+ * @see Intent#EXTRA_STRING_BREADCRUMB
+ */
+ public static final String ACTION_EDIT_CONDITION = "com.twofortyfouram.locale.intent.action.EDIT_CONDITION"; //$NON-NLS-1$
+
+ /**
+ * Ordered {@code Intent} action {@code String} broadcast by Locale to query a plug-in condition. When
+ * Locale broadcasts this {@code Intent}, it will be sent directly to the package and class of the
+ * plug-in's {@code BroadcastReceiver}. The {@code Intent} will contain a {@link #EXTRA_BUNDLE} that was
+ * previously set by the {@code Activity} result of {@link #ACTION_EDIT_CONDITION}.
+ *
+ * Since this is an ordered broadcast, the receiver is expected to set an appropriate result code from
+ * {@link #RESULT_CONDITION_SATISFIED}, {@link #RESULT_CONDITION_UNSATISFIED}, and
+ * {@link #RESULT_CONDITION_UNKNOWN}.
+ *
+ * There MUST be only one {@code BroadcastReceiver} per APK that implements this {@code Intent}.
+ *
+ * @see Intent#EXTRA_BUNDLE
+ * @see Intent#RESULT_CONDITION_SATISFIED
+ * @see Intent#RESULT_CONDITION_UNSATISFIED
+ * @see Intent#RESULT_CONDITION_UNKNOWN
+ */
+ public static final String ACTION_QUERY_CONDITION = "com.twofortyfouram.locale.intent.action.QUERY_CONDITION"; //$NON-NLS-1$
+
+ /**
+ * {@code Intent} action {@code String} to notify Locale that a plug-in condition is requesting that
+ * Locale query it via {@link #ACTION_QUERY_CONDITION}. This merely serves as a hint to Locale that a
+ * condition wants to be queried. There is no guarantee as to when or if the plug-in will be queried after
+ * this {@code Intent} is broadcast. If Locale does not respond to the plug-in condition after a
+ * {@link #ACTION_REQUEST_QUERY} Intent is sent, the plug-in SHOULD shut itself down and stop requesting
+ * requeries. A lack of response from Locale indicates that Locale is not currently interested in this
+ * plug-in. When Locale becomes interested in the plug-in again, Locale will send
+ * {@link #ACTION_QUERY_CONDITION}.
+ *
+ * The extra {@link #EXTRA_ACTIVITY} MUST be included, otherwise Locale will ignore this {@code Intent}.
+ *
+ * Plug-in conditions SHOULD NOT use this unless there is some sort of asynchronous event that has
+ * occurred, such as a broadcast {@code Intent} being received by the plug-in. Plug-ins SHOULD NOT
+ * periodically request a requery as a way of implementing polling behavior.
+ *
+ * @see Intent#EXTRA_ACTIVITY
+ */
+ public static final String ACTION_REQUEST_QUERY = "com.twofortyfouram.locale.intent.action.REQUEST_QUERY"; //$NON-NLS-1$
+
+ /**
+ * Type: {@code String}.
+ *
+ * Maps to a {@code String} that represents the {@code Activity} bread crumb path.
+ *
+ * @see BreadCrumber
+ */
+ public static final String EXTRA_STRING_BREADCRUMB = "com.twofortyfouram.locale.intent.extra.BREADCRUMB"; //$NON-NLS-1$
+
+ /**
+ * Type: {@code String}.
+ *
+ * Maps to a {@code String} that represents a blurb. This is returned as an {@code Activity} result extra
+ * from {@link #ACTION_EDIT_CONDITION} or {@link #ACTION_EDIT_SETTING}.
+ *
+ * The blurb is a concise description displayed to the user of what the plug-in is configured to do.
+ */
+ public static final String EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB"; //$NON-NLS-1$
+
+ /**
+ * Type: {@code Bundle}.
+ *
+ * Maps to a {@code Bundle} that contains all of a plug-in's extras.
+ *
+ * Plug-ins MUST NOT store {@link Parcelable} objects in this {@code Bundle}, because {@code Parcelable}
+ * is not a long-term storage format. Also, plug-ins MUST NOT store any serializable object that is not
+ * exposed by the Android SDK.
+ *
+ * The maximum size of a Bundle that can be sent across process boundaries is on the order of 500
+ * kilobytes (base-10), while Locale further limits plug-in Bundles to about 100 kilobytes (base-10).
+ * Although the maximum size is about 100 kilobytes, plug-ins SHOULD keep Bundles much smaller for
+ * performance and memory usage reasons.
+ */
+ public static final String EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"; //$NON-NLS-1$
+
+ /**
+ * Type: {@code String}.
+ *
+ * Maps to a {@code String} that represents the name of a plug-in's {@code Activity}.
+ *
+ * @see Intent#ACTION_REQUEST_QUERY
+ */
+ public static final String EXTRA_ACTIVITY = "com.twofortyfouram.locale.intent.extra.ACTIVITY"; //$NON-NLS-1$
+}
\ No newline at end of file
diff --git a/src/net/dinglisch/android/tasker/TaskerPlugin.java b/src/net/dinglisch/android/tasker/TaskerPlugin.java
new file mode 100644
index 0000000..6eaf4a4
--- /dev/null
+++ b/src/net/dinglisch/android/tasker/TaskerPlugin.java
@@ -0,0 +1,441 @@
+//package com.yourcompany.yoursetting;
+package net.dinglisch.android.tasker;
+
+import java.net.URISyntaxException;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+// Constants and functions for Tasker *extensions* to the plugin protocol
+// See Also: http://tasker.dinglisch.net/plugins.html
+// v1.0b5
+
+public class TaskerPlugin {
+
+ private final static String TAG = "TaskerPlugin";
+
+ private final static String BASE_KEY = "net.dinglisch.android.tasker";
+ private final static String EXTRAS_PREFIX = BASE_KEY + ".extras.";
+
+ /**
+ * @see #addVariableBundle(Bundle, Bundle)
+ * @see Host#getVariablesBundle(Intent)
+ */
+ private final static String EXTRA_VARIABLES_BUNDLE = EXTRAS_PREFIX + "VARIABLES";
+
+ /**
+ * Host capabilities, passed to plugin with condition or setting edit intents
+ */
+ private final static String EXTRA_HOST_CAPABILITIES = EXTRAS_PREFIX + "HOST_CAPABILITIES";
+
+ /**
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES = 2;
+
+ /**
+ * @see Condition#hostSupportsVariableReturn(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES = 4;
+
+ /**
+ * @see Setting#hostSupportsOnFireVariableReplacement(Bundle)
+ */
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT = 8;
+
+ /**
+ * @see Setting#hostSupportsVariableReturn(Bundle)
+ */
+ private final static int EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES = 16;
+
+ /**
+ *
+ */
+ public final static int EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION = 32;
+
+
+ public final static int EXTRA_HOST_CAPABILITY_ALL =
+ EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES |
+ EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES |
+ EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT |
+ EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES|
+ EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION;
+ ;
+
+ //
+ /**
+ *
+ * @see #hostSupportsRelevantVariables(Bundle)
+ * @see #addRelevantVariableList(Bundle, String[])
+ * @see #getRelevantVariableList(Bundle)
+ */
+ private final static String BUNDLE_KEY_RELEVANT_VARIABLES = BASE_KEY + ".RELEVANT_VARIABLES";
+
+ public static boolean hostSupportsRelevantVariables( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES );
+ }
+
+ /**
+ * Specifies to host which variables might be used by the plugin.
+ *
+ * Used in EditActivity, before setResult().
+ *
+ * @param intentToHost the intent being returned to the host
+ * @param variableNames array of relevant variable names
+ */
+ public static void addRelevantVariableList( Intent intentToHost, String [] variableNames ) {
+ intentToHost.putExtra( BUNDLE_KEY_RELEVANT_VARIABLES, variableNames );
+ }
+
+ /**
+ * Allows the plugin/host to indicate to each other a set of variables which they are referencing.
+ * The host may use this to e.g. show a variable selection list in it's UI.
+ * The host should use this if it previously indicated to the plugin that it supports relevant vars
+ *
+ * @param fromHostIntentExtras usually from getIntent().getExtras()
+ * @return variableNames an array of relevant variable names
+ */
+ public static String [] getRelevantVariableList( Bundle fromHostIntentExtras ) {
+
+ String [] relevantVars = (String []) getBundleValueSafe( fromHostIntentExtras, BUNDLE_KEY_RELEVANT_VARIABLES, String [].class, "getRelevantVariableList" );
+
+ if ( relevantVars == null )
+ relevantVars = new String [0];
+
+ return relevantVars;
+ }
+
+ /**
+ * Used by: plugin QueryReceiver, FireReceiver
+ *
+ * Add a bundle of variable name/value pairs.
+ *
+ * @param resultExtras the result extras from the receiver onReceive (from a call to getResultExtras())
+ * @param variables the variables to send
+ * @see #hostSupportsVariableReturn(Bundle)
+ */
+ public static void addVariableBundle( Bundle resultExtras, Bundle variables ) {
+ resultExtras.putBundle( EXTRA_VARIABLES_BUNDLE, variables );
+ }
+
+ // ----------------------------- SETTING PLUGIN ONLY --------------------------------- //
+
+ public static class Setting {
+
+ /**
+ * @see #setVariableReplaceKeys(Bundle, String[])
+ */
+ private final static String BUNDLE_KEY_VARIABLE_REPLACE_STRINGS = EXTRAS_PREFIX + "VARIABLE_REPLACE_KEYS";
+
+ /**
+ * @see #requestTimeoutMS(Intent, int)
+ */
+ private final static String EXTRA_REQUESTED_TIMEOUT = EXTRAS_PREFIX + "REQUESTED_TIMEOUT";
+
+ /**
+ * @see #signalFinish(Context, Intent, Status, Bundle)
+ * @see Host#addCompletionIntent(Intent, Intent)
+ */
+ private final static String EXTRA_PLUGIN_COMPLETION_INTENT = EXTRAS_PREFIX + "COMPLETION_INTENT";
+
+ /**
+ * @see #signalFinish(Context, Intent, Status, Bundle)
+ * @see Host#getSettingCompletionStatus(Intent)
+ */
+ public final static String EXTRA_RESULT_CODE = EXTRAS_PREFIX + "RESULT_CODE";
+
+ /**
+ * @see #signalFinish(Context, Intent, Status, Bundle)
+ * @see Host#getSettingResultCode(Intent)
+ */
+
+ public final static int RESULT_CODE_OK = Activity.RESULT_OK;
+ public final static int RESULT_CODE_OK_MINOR_FAILURES = Activity.RESULT_FIRST_USER;
+ public final static int RESULT_CODE_FAILED = Activity.RESULT_FIRST_USER + 1;
+ public final static int RESULT_CODE_PENDING = Activity.RESULT_FIRST_USER + 2;
+ public final static int RESULT_CODE_UNKNOWN = Activity.RESULT_FIRST_USER + 3;
+
+ /**
+ * Used by: plugin EditActivity.
+ *
+ * Indicates to plugin that host will replace variables in specified bundle keys.
+ *
+ * Replacement takes place every time the setting is fired, before the bundle is
+ * passed to the plugin FireReceiver.
+ *
+ * @param extrasFromHost intent extras from the intent received by the edit activity
+ * @see #setVariableReplaceKeys(Bundle, String[])
+ */
+ public static boolean hostSupportsOnFireVariableReplacement( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT );
+ }
+
+ public static boolean hostSupportsSynchronousExecution( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION );
+ }
+
+ /**
+ * Request the host to wait the specified number of milliseconds before continuing.
+ * Note that the host may choose to ignore the request.
+ *
+ * Used in EditActivity, before setResult().
+ *
+ * @param intentToHost the intent being returned to the host
+ * @param timeoutMS
+ */
+ public static void requestTimeoutMS( Intent intentToHost, int timeoutMS ) {
+ intentToHost.putExtra( EXTRA_REQUESTED_TIMEOUT, timeoutMS );
+ }
+
+ /**
+ * Used by: plugin EditActivity
+ *
+ * Indicates to host which bundle keys should be replaced.
+ *
+ * @param resultBundleToHost the bundle being returned to the host
+ * @param listOfKeyNames which bundle keys to replace variables in when setting fires
+ * @see #hostSupportsOnFireVariableReplacement(Bundle)
+ */
+ public static void setVariableReplaceKeys( Bundle resultBundleToHost, String [] listOfKeyNames ) {
+
+ StringBuilder builder = new StringBuilder();
+
+ if ( listOfKeyNames != null ) {
+
+ for ( String keyName : listOfKeyNames ) {
+
+ if ( keyName.contains( " " ) )
+ Log.w( TAG, "setVariableReplaceKeys: ignoring bad keyName containing space: " + keyName );
+ else {
+ if ( builder.length() > 0 )
+ builder.append( ' ' );
+
+ builder.append( keyName );
+ }
+
+ if ( builder.length() > 0 )
+ resultBundleToHost.putString( BUNDLE_KEY_VARIABLE_REPLACE_STRINGS, builder.toString() );
+ }
+ }
+ }
+
+ /**
+ * Used by: plugin FireReceiver
+ *
+ * Indicates to plugin whether the host will process variables which it passes back
+ *
+ * @param extrasFromHost intent extras from the intent received by the FireReceiver
+ * @see #signalFinish(Context, Intent, Status, Bundle)
+ */
+ public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES );
+ }
+
+ /**
+ * Used by: plugin FireReceiver
+ *
+ * Tell the host that the plugin has finished execution.
+ *
+ * @param originalFireIntent the intent received from the host (via onReceive())
+ * @param status level of success in performing the settings
+ * @param vars any variables that the plugin wants to set in the host
+ * @see #hostSupportsSynchronousSettings(Bundle)
+ * @see #setWantSynchronousExecution(Intent, int)
+ */
+ public static boolean signalFinish( Context context, Intent originalFireIntent, int resultCode, Bundle vars ) {
+
+ String errorPrefix = "signalFinish: ";
+
+ boolean okFlag = false;
+
+ String completionIntentString = (String) TaskerPlugin.getExtraValueSafe( originalFireIntent, Setting.EXTRA_PLUGIN_COMPLETION_INTENT, String.class, "signalFinish" );
+
+ if ( completionIntentString != null ) {
+ Uri completionIntentUri = null;
+ try {
+ completionIntentUri = Uri.parse( completionIntentString );
+ }
+ // should only throw NullPointer but don't particularly trust it
+ catch ( Exception e ) {
+ Log.w( TAG, errorPrefix + "couldn't parse " + completionIntentString );
+ }
+
+ if ( completionIntentUri != null ) {
+ try {
+ Intent completionIntent = Intent.parseUri( completionIntentString, Intent.URI_INTENT_SCHEME );
+
+ completionIntent.putExtra( EXTRA_RESULT_CODE, resultCode );
+
+ if ( vars != null )
+ completionIntent.putExtra( EXTRA_VARIABLES_BUNDLE, vars );
+
+ context.sendBroadcast( completionIntent );
+
+ okFlag = true;
+ }
+ catch ( URISyntaxException e ) {
+ Log.w( TAG, errorPrefix + "bad URI: " + completionIntentUri );
+ }
+ }
+ }
+
+ return okFlag;
+ }
+ }
+
+ // ----------------------------- CONDITION PLUGIN ONLY --------------------------------- //
+
+ public static class Condition {
+
+ /**
+ * Used by: plugin QueryReceiver
+ *
+ * Indicates to plugin whether the host will process variables which it passes back
+ *
+ * @param extrasFromHost intent extras from the intent received by the QueryReceiver
+ * @see #addVariableBundle(Bundle, Bundle)
+ */
+ public static boolean hostSupportsVariableReturn( Bundle extrasFromHost ) {
+ return hostSupports( extrasFromHost, EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES );
+ }
+ }
+
+ // ---------------------------------- HOST ----------------------------------------- //
+
+ public static class Host {
+
+ /**
+ * Tell the plugin what capabilities the host support. This should be called when sending
+ * intents to any EditActivity, FireReceiver or QueryReceiver.
+ *
+ * @param toPlugin the intent we're sending
+ * @return capabilites one or more of the EXTRA_HOST_CAPABILITY_XXX flags
+ */
+ public static Intent addCapabilities( Intent toPlugin, int capabilities ) {
+ return toPlugin.putExtra( EXTRA_HOST_CAPABILITIES, capabilities );
+ }
+
+ /**
+ * Add an intent to the fire intent before it goes to the plugin FireReceiver, which the plugin
+ * can use to signal when it is finished. Only use if @code{pluginWantsSychronousExecution} is true.
+ *
+ * @param fireIntent fire intent going to the plugin
+ * @param completionIntent intent which will signal the host that the plugin is finished.
+ * Implementation is host-dependent.
+ */
+ public static void addCompletionIntent( Intent fireIntent, Intent completionIntent ) {
+ fireIntent.putExtra(
+ Setting.EXTRA_PLUGIN_COMPLETION_INTENT,
+ completionIntent.toUri( Intent.URI_INTENT_SCHEME )
+ );
+ }
+
+ /**
+ * When a setting plugin is finished, it sends the host the intent which was passed to it
+ * via @code{addCompletionIntent}.
+ *
+ * @param completionIntent intent returned from the plugin when it finished.
+ * @return completionStatus measure of plugin success, defaults to UNKNOWN
+ */
+ public static int getSettingResultCode( Intent completionIntent ) {
+
+ Integer val = (Integer) getExtraValueSafe( completionIntent, Setting.EXTRA_RESULT_CODE, String.class, "getSettingCompletionStatus" );
+
+ return ( val == null ) ? Setting.RESULT_CODE_UNKNOWN : val;
+ }
+
+ /**
+ * Extract a bundle of variables from an intent received from the FireReceiver. This
+ * should be called if the host previously indicated to the plugin
+ * that it supports setting variable return.
+ *
+ * @param fromPlugin the intent we received
+ * @return variables a bundle of variable name/value pairs
+ * @see #addCapabilities(Intent, int)
+ */
+
+ public static Bundle getVariablesBundle( Bundle resultExtras ) {
+ return (Bundle) getBundleValueSafe(
+ resultExtras, EXTRA_VARIABLES_BUNDLE, Bundle.class, "getVariablesBundle"
+ );
+ }
+
+ public static boolean haveRequestedTimeout( Bundle extrasFromPluginEditActivity ) {
+ return extrasFromPluginEditActivity.containsKey( Setting.EXTRA_REQUESTED_TIMEOUT );
+ }
+
+ public static int getRequestedTimeoutMS( Bundle extrasFromPluginEditActivity ) {
+ return
+ (Integer) getBundleValueSafe(
+ extrasFromPluginEditActivity, Setting.EXTRA_REQUESTED_TIMEOUT, Integer.class, "getRequestedTimeout"
+ )
+ ;
+ }
+
+ public static String [] getSettingVariableReplaceKeys( Bundle fromPluginEditActivity ) {
+
+ String spec = (String) TaskerPlugin.getBundleValueSafe( fromPluginEditActivity, Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS, String.class, "getSettingVariableReplaceKeys" );
+
+ String [] replaceKeys = null;
+
+ if ( spec != null )
+ replaceKeys = spec.split( " " );
+
+ return replaceKeys;
+ }
+
+ public static boolean haveRelevantVariables( Bundle b ) {
+ return b.containsKey( BUNDLE_KEY_RELEVANT_VARIABLES );
+ }
+
+ public static void cleanRelevantVariables( Bundle b ) {
+ b.remove( BUNDLE_KEY_RELEVANT_VARIABLES );
+ }
+
+ public static void cleanRequestedTimeout( Bundle extras ) {
+ extras.remove( Setting.EXTRA_REQUESTED_TIMEOUT );
+ }
+
+ public static void cleanSettingReplaceVariables( Bundle b ) {
+ b.remove( Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS );
+ }
+ }
+
+ // ---------------------------------- HELPER FUNCTIONS -------------------------------- //
+
+ private static Object getBundleValueSafe( Bundle b, String key, Class> expectedClass, String funcName ) {
+ Object value = null;
+
+ if ( b != null ) {
+ if ( b.containsKey( key ) ) {
+ Object obj = b.get( key );
+ if ( obj == null )
+ Log.w( TAG, funcName + ": " + key + ": null value" );
+ else if ( obj.getClass() != expectedClass )
+ Log.w( TAG, funcName + ": " + key + ": expected " + expectedClass.getClass().getName() + ", got " + obj.getClass().getName() );
+ else
+ value = obj;
+ }
+ }
+ return value;
+ }
+
+ private static Object getExtraValueSafe( Intent i, String key, Class> expectedClass, String funcName ) {
+ return ( i.hasExtra( key ) ) ?
+ getBundleValueSafe( i.getExtras(), key, expectedClass, funcName ) :
+ null;
+ }
+
+ private static boolean hostSupports( Bundle extrasFromHost, int capabilityFlag ) {
+ Integer flags = (Integer) getBundleValueSafe( extrasFromHost, EXTRA_HOST_CAPABILITIES, Integer.class, "hostSupports" );
+ return
+ ( flags != null ) &&
+ ( ( flags & capabilityFlag ) > 0 )
+ ;
+ }
+
+}
\ No newline at end of file