diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cbac75b2..2d20375b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,6 +27,7 @@
+
@@ -89,8 +90,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/project.properties b/project.properties
index 73f48d3c..f7f1e96d 100755
--- a/project.properties
+++ b/project.properties
@@ -12,3 +12,4 @@ split.density=false
# Project target.
target=android-16
android.library.reference.1=../xbmc-jsonrpclib-android
+android.library.reference.2=../SmartExtensionUtils
diff --git a/res/drawable/ext_icon.png b/res/drawable/ext_icon.png
new file mode 100644
index 00000000..b9b2289c
Binary files /dev/null and b/res/drawable/ext_icon.png differ
diff --git a/res/drawable/ext_icon_bw.png b/res/drawable/ext_icon_bw.png
new file mode 100644
index 00000000..8c8194c1
Binary files /dev/null and b/res/drawable/ext_icon_bw.png differ
diff --git a/src/org/xbmc/android/smartextension/XbmcExtensionReceiver.java b/src/org/xbmc/android/smartextension/XbmcExtensionReceiver.java
new file mode 100644
index 00000000..f2ad4b3a
--- /dev/null
+++ b/src/org/xbmc/android/smartextension/XbmcExtensionReceiver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 Cedric Priscal
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC Remote; see the file license. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+package org.xbmc.android.smartextension;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION;
+
+public class XbmcExtensionReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (VERSION.SDK_INT >= 7) {
+ intent.setClass(context, XbmcExtensionService.class);
+ context.startService(intent);
+ }
+ }
+
+}
diff --git a/src/org/xbmc/android/smartextension/XbmcExtensionRegistrationInformation.java b/src/org/xbmc/android/smartextension/XbmcExtensionRegistrationInformation.java
new file mode 100644
index 00000000..6475989f
--- /dev/null
+++ b/src/org/xbmc/android/smartextension/XbmcExtensionRegistrationInformation.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2012 Cedric Priscal
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC Remote; see the file license. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+package org.xbmc.android.smartextension;
+
+import android.content.ContentValues;
+import android.content.Context;
+
+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;
+import com.sonyericsson.extras.liveware.sdk.R;
+
+public class XbmcExtensionRegistrationInformation extends
+ RegistrationInformation {
+
+ final Context mContext;
+
+ protected XbmcExtensionRegistrationInformation(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context == null");
+ }
+ mContext = context;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.extension.util.registration.
+ * RegistrationInformation#getRequiredNotificationApiVersion()
+ */
+ @Override
+ public int getRequiredNotificationApiVersion() {
+ return API_NOT_REQUIRED;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.extension.util.registration.
+ * RegistrationInformation#getExtensionRegistrationConfiguration()
+ */
+ @Override
+ public ContentValues getExtensionRegistrationConfiguration() {
+ ContentValues values = new ContentValues();
+
+ values.put(Registration.ExtensionColumns.NAME,
+ mContext.getString(R.string.app_name));
+ values.put(Registration.ExtensionColumns.EXTENSION_KEY,
+ XbmcExtensionService.EXTENSION_KEY);
+ values.put(Registration.ExtensionColumns.HOST_APP_ICON_URI,
+ ExtensionUtils.getUriString(mContext, org.xbmc.android.remote.R.drawable.icon));
+ values.put(Registration.ExtensionColumns.EXTENSION_ICON_URI,
+ ExtensionUtils.getUriString(mContext, org.xbmc.android.remote.R.drawable.ext_icon));
+ values.put(
+ Registration.ExtensionColumns.EXTENSION_ICON_URI_BLACK_WHITE,
+ ExtensionUtils.getUriString(mContext, org.xbmc.android.remote.R.drawable.ext_icon_bw));
+ values.put(Registration.ExtensionColumns.NOTIFICATION_API_VERSION,
+ getRequiredNotificationApiVersion());
+ values.put(Registration.ExtensionColumns.PACKAGE_NAME,
+ mContext.getPackageName());
+
+ return values;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.extension.util.registration.
+ * RegistrationInformation#getRequiredWidgetApiVersion()
+ */
+ @Override
+ public int getRequiredWidgetApiVersion() {
+ return API_NOT_REQUIRED;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.extension.util.registration.
+ * RegistrationInformation#getRequiredControlApiVersion()
+ */
+ @Override
+ public int getRequiredControlApiVersion() {
+ return 1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.sonyericsson.extras.liveware.extension.util.registration.
+ * RegistrationInformation#getRequiredSensorApiVersion()
+ */
+ @Override
+ public int getRequiredSensorApiVersion() {
+ return API_NOT_REQUIRED;
+ }
+
+ @Override
+ public boolean isDisplaySizeSupported(int width, int height) {
+ return true;
+ }
+}
diff --git a/src/org/xbmc/android/smartextension/XbmcExtensionService.java b/src/org/xbmc/android/smartextension/XbmcExtensionService.java
new file mode 100644
index 00000000..5ef01169
--- /dev/null
+++ b/src/org/xbmc/android/smartextension/XbmcExtensionService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 Cedric Priscal
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC Remote; see the file license. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+package org.xbmc.android.smartextension;
+
+import android.util.Log;
+
+import com.sonyericsson.extras.liveware.extension.util.ExtensionService;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlExtension;
+import com.sonyericsson.extras.liveware.extension.util.registration.RegistrationInformation;
+
+public class XbmcExtensionService extends ExtensionService {
+
+ public static final String EXTENSION_KEY = "org.xbmc.android.smartextension";
+ private static final String LOG_TAG = "XbmcExtensionService";
+
+ public XbmcExtensionService() {
+ super(EXTENSION_KEY);
+ Log.d(LOG_TAG, "Starting XbmcExtensionService");
+ }
+
+ /* (non-Javadoc)
+ * @see com.sonyericsson.extras.liveware.extension.util.ExtensionService#getRegistrationInformation()
+ */
+ @Override
+ protected RegistrationInformation getRegistrationInformation() {
+ return new XbmcExtensionRegistrationInformation(this);
+ }
+
+ /* (non-Javadoc)
+ * @see com.sonyericsson.extras.liveware.extension.util.ExtensionService#keepRunningWhenConnected()
+ */
+ @Override
+ protected boolean keepRunningWhenConnected() {
+ return false;
+ }
+
+ /* (non-Javadoc)
+ * @see com.sonyericsson.extras.liveware.extension.util.ExtensionService#createControlExtension()
+ */
+ @Override
+ public ControlExtension createControlExtension(String hostAppPackageName) {
+ return new XbmcSmartWatchControlExtension(this, hostAppPackageName);
+ }
+
+}
diff --git a/src/org/xbmc/android/smartextension/XbmcSmartWatchControlExtension.java b/src/org/xbmc/android/smartextension/XbmcSmartWatchControlExtension.java
new file mode 100644
index 00000000..7fb7279a
--- /dev/null
+++ b/src/org/xbmc/android/smartextension/XbmcSmartWatchControlExtension.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2012 Cedric Priscal
+ *
+ * This Program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This Program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with XBMC Remote; see the file license. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+package org.xbmc.android.smartextension;
+
+import org.xbmc.android.remote.R;
+import org.xbmc.android.remote.business.ManagerFactory;
+import org.xbmc.android.remote.business.NowPlayingPollerThread;
+import org.xbmc.android.util.ConnectionFactory;
+import org.xbmc.api.business.DataResponse;
+import org.xbmc.api.business.IControlManager;
+import org.xbmc.api.business.IEventClientManager;
+import org.xbmc.api.data.IControlClient.ICurrentlyPlaying;
+import org.xbmc.api.info.PlayStatus;
+import org.xbmc.eventclient.ButtonCodes;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.sonyericsson.extras.liveware.aef.control.Control;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlExtension;
+import com.sonyericsson.extras.liveware.extension.util.control.ControlTouchEvent;
+
+public class XbmcSmartWatchControlExtension extends ControlExtension implements
+ Callback {
+
+ // DEBUG
+ private static final String LOG_TAG = "XbmcSmartWatchControlExtension";
+ // Connection with XBMC
+ private final IEventClientManager mEventClientManager;
+ private final IControlManager mControlManager;
+ // PLAYING STATUS
+ private Handler mNowPlayingHandler;
+ private int mLastPosition;
+ private int mPlayStatus;
+ private int mPlayListId;
+ //private String mTitle;
+ //private String mArtist;
+ //private String mAlbum;
+ //private int mProgressPosition;
+ // GUI
+ private final int mWidth;
+ private final int mHeight;
+ private final LinearLayout mLayout;
+ private final Bitmap mBackground;
+ private final Canvas mCanvas;
+ // private final TextView mTitleView;
+ private final ImageView mCoverView;
+ private final LayoutParams mLayoutParams;
+
+ public XbmcSmartWatchControlExtension(Context context,
+ String hostAppPackageName) {
+ super(context, hostAppPackageName);
+ mNowPlayingHandler = new Handler(this);
+ mControlManager = ManagerFactory.getControlManager(null);
+ mEventClientManager = ManagerFactory.getEventClientManager(null);
+ mWidth = context.getResources().getDimensionPixelSize(
+ com.sonyericsson.extras.liveware.extension.util.R.dimen.smart_watch_control_width);
+ mHeight = context.getResources().getDimensionPixelSize(
+ com.sonyericsson.extras.liveware.extension.util.R.dimen.smart_watch_control_height);
+ mLayoutParams = new LayoutParams(mWidth, mHeight);
+
+ // Create background bitmap for drawing.
+ mBackground = Bitmap.createBitmap(mWidth, mHeight,
+ Bitmap.Config.RGB_565);
+ // Set default density to avoid scaling.
+ mBackground.setDensity(DisplayMetrics.DENSITY_DEFAULT);
+ mCanvas = new Canvas(mBackground);
+
+ mLayout = new LinearLayout(context);
+ // mTitleView = new TextView(context);
+ mCoverView = new ImageView(context);
+ mCoverView.setImageResource(R.drawable.coverbox_back);
+ mCoverView.setLayoutParams(mLayoutParams);
+
+ mLayout.setLayoutParams(mLayoutParams);
+ mLayout.addView(mCoverView);
+ // mLayout.addView(mTitleView);
+ }
+
+ @Override
+ public void onResume() {
+ setScreenState(Control.Intents.SCREEN_STATE_ON);
+ refresh();
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ @Override
+ public void onStart() {
+ new Thread("nowplaying-spawning") {
+ @Override
+ public void run() {
+ ConnectionFactory.getNowPlayingPoller(
+ mContext.getApplicationContext()).subscribe(
+ mNowPlayingHandler);
+ }
+ }.start();
+ }
+
+ @Override
+ public void onStop() {
+ ConnectionFactory.getNowPlayingPoller(mContext.getApplicationContext())
+ .unSubscribe(mNowPlayingHandler);
+ }
+
+ private void refresh() {
+ mLayout.measure(mWidth, mHeight);
+ mLayout.layout(0, 0, mLayout.getMeasuredWidth(),
+ mLayout.getMeasuredHeight());
+ mBackground.eraseColor(0);
+ mLayout.draw(mCanvas);
+
+ showBitmap(mBackground);
+ }
+
+ @Override
+ public void onTouch(ControlTouchEvent event) {
+ switch (event.getAction()) {
+ case Control.Intents.TOUCH_ACTION_RELEASE:
+ // Play/Pause
+ switch (mPlayStatus) {
+ case PlayStatus.PLAYING:
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_PAUSE,
+ false, true, true, (short) 0, (byte) 0);
+ break;
+ case PlayStatus.PAUSED:
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_PLAY,
+ false, true, true, (short) 0, (byte) 0);
+ break;
+ case PlayStatus.STOPPED:
+ final DataResponse doNothing = new DataResponse();
+ mControlManager.setPlaylistId(doNothing, mPlayListId < 0 ? 0
+ : mPlayListId, mContext.getApplicationContext());
+ mControlManager.setPlaylistPos(doNothing, mLastPosition < 0 ? 0
+ : mLastPosition, mContext.getApplicationContext());
+ break;
+ }
+ break;
+ case Control.Intents.TOUCH_ACTION_LONGPRESS:
+ // Stop
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_STOP, false, true, true,
+ (short) 0, (byte) 0);
+ setScreenState(Control.Intents.SCREEN_STATE_AUTO);
+ break;
+ }
+ }
+
+ @Override
+ public void onSwipe(int direction) {
+ switch (direction) {
+ case Control.Intents.SWIPE_DIRECTION_RIGHT:
+ // forward
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_RIGHT, false, true, true,
+ (short) 0, (byte) 0);
+ break;
+
+ case Control.Intents.SWIPE_DIRECTION_LEFT:
+ // backward
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_LEFT, false, true, true,
+ (short) 0, (byte) 0);
+ break;
+
+ case Control.Intents.SWIPE_DIRECTION_UP:
+ // Previous element in playlist
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_SKIP_MINUS, false, true, true,
+ (short) 0, (byte) 0);
+ break;
+
+ case Control.Intents.SWIPE_DIRECTION_DOWN:
+ // Next element in playlist
+ mEventClientManager.sendButton("R1", ButtonCodes.REMOTE_SKIP_PLUS, false, true, true,
+ (short) 0, (byte) 0);
+ break;
+ }
+ }
+
+ public boolean handleMessage(Message msg) {
+
+ final Bundle data = msg.getData();
+ final ICurrentlyPlaying currentlyPlaying = (ICurrentlyPlaying) data
+ .getSerializable(NowPlayingPollerThread.BUNDLE_CURRENTLY_PLAYING);
+
+ Log.d(LOG_TAG, "handleMessage: " + msg.what);
+
+ switch (msg.what) {
+
+ case NowPlayingPollerThread.MESSAGE_PROGRESS_CHANGED:
+ mPlayStatus = currentlyPlaying.getPlayStatus();
+ //mProgressPosition = Math.round(currentlyPlaying.getPercentage());
+ if (currentlyPlaying.isPlaying()) {
+ Log.d(LOG_TAG, "PROGRESS_CHANGED => play");
+ } else {
+ Log.d(LOG_TAG, "PROGRESS_CHANGED => stop/pause");
+ }
+ refresh();
+ return true;
+
+ case NowPlayingPollerThread.MESSAGE_PLAYLIST_ITEM_CHANGED:
+ //mTitle = currentlyPlaying.getTitle();
+ //mArtist = currentlyPlaying.getArtist();
+ //mAlbum = currentlyPlaying.getAlbum();
+ mLastPosition = data
+ .getInt(NowPlayingPollerThread.BUNDLE_LAST_PLAYPOSITION);
+ refresh();
+ return true;
+
+ case NowPlayingPollerThread.MESSAGE_PLAYSTATE_CHANGED:
+ mPlayListId = data
+ .getInt(NowPlayingPollerThread.BUNDLE_LAST_PLAYLIST);
+ refresh();
+ return true;
+
+ case NowPlayingPollerThread.MESSAGE_COVER_CHANGED:
+ Bitmap b = ConnectionFactory.getNowPlayingPoller(
+ mContext.getApplicationContext()).getNowPlayingCover();
+ if (b != null) {
+ mCoverView.setImageBitmap(b);
+ } else {
+ mCoverView.setImageResource(R.drawable.coverbox_back);
+ }
+ refresh();
+ return true;
+
+ case NowPlayingPollerThread.MESSAGE_CONNECTION_ERROR:
+ mPlayStatus = PlayStatus.UNKNOWN;
+ Log.w(LOG_TAG, "Received connection error from poller!");
+ refresh();
+ return true;
+
+ case NowPlayingPollerThread.MESSAGE_RECONFIGURE:
+ mPlayStatus = PlayStatus.UNKNOWN;
+ new Thread() {
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, Log.getStackTraceString(e));
+ }
+ ConnectionFactory.getNowPlayingPoller(
+ mContext.getApplicationContext()).subscribe(
+ mNowPlayingHandler);
+ }
+ }.start();
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+}