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; + } + } + +}