diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9533e0c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+bin/
+gen/
+libs/
+.metadata/
+*.apk
+*.ap_
+*.dex
+*.class
+.classpath
+.project
+.settings/
+proguard/
+project.properties
+proguard-project.txt
diff --git a/slideshow/android/SlideshowCast/AndroidManifest.xml b/slideshow/android/SlideshowCast/AndroidManifest.xml
new file mode 100644
index 0000000..147331c
--- /dev/null
+++ b/slideshow/android/SlideshowCast/AndroidManifest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_launcher.png b/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..070c890
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_launcher.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_user.png b/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_user.png
new file mode 100644
index 0000000..bc33709
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-hdpi/ic_user.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_launcher.png b/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9bd87fc
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_launcher.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_user.png b/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_user.png
new file mode 100644
index 0000000..1c831de
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-mdpi/ic_user.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_launcher.png b/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..02bd221
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_user.png b/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_user.png
new file mode 100644
index 0000000..589ed7e
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-xhdpi/ic_user.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_launcher.png b/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..02bf372
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_user.png b/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_user.png
new file mode 100644
index 0000000..29260f2
Binary files /dev/null and b/slideshow/android/SlideshowCast/res/drawable-xxhdpi/ic_user.png differ
diff --git a/slideshow/android/SlideshowCast/res/layout/main.xml b/slideshow/android/SlideshowCast/res/layout/main.xml
new file mode 100644
index 0000000..63111c4
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/layout/main.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/slideshow/android/SlideshowCast/res/menu/menu.xml b/slideshow/android/SlideshowCast/res/menu/menu.xml
new file mode 100644
index 0000000..baee3fe
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/menu/menu.xml
@@ -0,0 +1,8 @@
+
diff --git a/slideshow/android/SlideshowCast/res/values-sw600dp/dimens.xml b/slideshow/android/SlideshowCast/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..886b05f
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values-sw600dp/dimens.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/slideshow/android/SlideshowCast/res/values-sw720dp-land/dimens.xml b/slideshow/android/SlideshowCast/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..00059fc
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 128dp
+
diff --git a/slideshow/android/SlideshowCast/res/values-v11/styles.xml b/slideshow/android/SlideshowCast/res/values-v11/styles.xml
new file mode 100644
index 0000000..189791f
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values-v11/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/slideshow/android/SlideshowCast/res/values-v14/styles.xml b/slideshow/android/SlideshowCast/res/values-v14/styles.xml
new file mode 100644
index 0000000..18783c7
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values-v14/styles.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/slideshow/android/SlideshowCast/res/values/dimens.xml b/slideshow/android/SlideshowCast/res/values/dimens.xml
new file mode 100644
index 0000000..47c8224
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+ 16dp
+ 16dp
+
diff --git a/slideshow/android/SlideshowCast/res/values/strings.xml b/slideshow/android/SlideshowCast/res/values/strings.xml
new file mode 100644
index 0000000..3ecab2a
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+
+
+ Slideshow for Chomecast
+ Settings
+ Add
+ Enter a URL
+ Controls
+ Albums
+ Sign out
+ Revoke access
+
diff --git a/slideshow/android/SlideshowCast/res/values/styles.xml b/slideshow/android/SlideshowCast/res/values/styles.xml
new file mode 100644
index 0000000..9e3012d
--- /dev/null
+++ b/slideshow/android/SlideshowCast/res/values/styles.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/AsyncReceiver.java b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/AsyncReceiver.java
new file mode 100644
index 0000000..ee0bcad
--- /dev/null
+++ b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/AsyncReceiver.java
@@ -0,0 +1,5 @@
+package at.foldedsoft.slideshowcast;
+
+public abstract class AsyncReceiver {
+ public abstract void finished(int results);
+}
diff --git a/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/MainActivity.java b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/MainActivity.java
new file mode 100644
index 0000000..4ca8393
--- /dev/null
+++ b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/MainActivity.java
@@ -0,0 +1,287 @@
+package at.foldedsoft.slideshowcast;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.SignInButton;
+import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
+import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
+import com.google.android.gms.plus.PlusClient;
+import com.google.android.gms.plus.PlusClient.OnAccessRevokedListener;
+import com.google.android.gms.plus.model.people.Person;
+import com.google.cast.CastContext;
+import com.google.cast.CastDevice;
+import com.google.cast.MediaRouteAdapter;
+import com.google.cast.MediaRouteHelper;
+import com.google.cast.MediaRouteStateChangeListener;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v7.app.MediaRouteButton;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
+import android.support.v7.media.MediaRouter.RouteInfo;
+
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.GridView;
+import android.widget.TextView;
+import android.content.Intent;
+import android.content.IntentSender;
+
+public class MainActivity extends FragmentActivity
+ implements ConnectionCallbacks,
+ OnConnectionFailedListener,
+ View.OnClickListener,
+ OnAccessRevokedListener,
+ MediaRouteAdapter {
+
+ private static final int REQUEST_CODE_SIGN_IN = 1;
+
+ private PlusClient mPlusClient;
+ private ConnectionResult mConnectionResult;
+ private Menu mMenu;
+ private SignInButton mSignInButton;
+ private View mContent;
+ private TextView mUserInfo;
+ private GridView mGridView;
+ private boolean mConnected = false;
+ private CharSequence mUserId;
+ private CharSequence mUserName;
+ private CharSequence mUserAccount;
+ private SearchReceiver mReceiver = new SearchReceiver();
+
+ private CastContext mCastContext;
+ private MediaRouteButton mMediaRouteButton;
+ private MediaRouter mMediaRouter;
+ private MediaRouteSelector mMediaRouteSelector;
+ private MediaRouter.Callback mMediaRouterCallback;
+ private CastDevice mSelectedDevice;
+ private MediaRouteStateChangeListener mRouteStateListener;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mSignInButton = (SignInButton) findViewById(R.id.sign_in_button);
+ mSignInButton.setOnClickListener(this);
+
+ mContent = findViewById(R.id.content);
+ mUserInfo = (TextView) findViewById(R.id.user_info);
+ mGridView = (GridView) findViewById(R.id.album_grid);
+
+ mPlusClient =
+ new PlusClient.Builder(this, this, this)
+ .setScopes("https://www.googleapis.com/auth/plus.login",
+ "https://picasaweb.google.com/data/")
+ .build();
+
+ mCastContext = new CastContext(this);
+ MediaRouteHelper.registerMinimalMediaRouteProvider(mCastContext, this);
+ mMediaRouter = MediaRouter.getInstance(this);
+ mMediaRouteSelector = MediaRouteHelper.buildMediaRouteSelector(
+ MediaRouteHelper.CATEGORY_CAST);
+ mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
+ mMediaRouteButton.setRouteSelector(mMediaRouteSelector);
+ mMediaRouterCallback = new MyMediaRouterCallback();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu, menu);
+ mMenu = menu;
+ mMenu.findItem(R.id.action_signout).setVisible(mConnected);
+ mMenu.findItem(R.id.action_revoke).setVisible(mConnected);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // handle item selection
+ switch (item.getItemId()) {
+ case R.id.action_signout:
+ if (mPlusClient.isConnected()) {
+ mPlusClient.clearDefaultAccount();
+ mPlusClient.disconnect();
+ mConnected = false;
+ mUserId = null;
+ mUserName = null;
+ mUserAccount = null;
+ updateUI();
+ mPlusClient.connect();
+ }
+ return true;
+ case R.id.action_revoke:
+ if (mPlusClient.isConnected()) {
+ mPlusClient.revokeAccessAndDisconnect(this);
+ mConnected = false;
+ mUserId = null;
+ mUserName = null;
+ mUserAccount = null;
+ updateUI();
+ }
+ return true;
+ }
+ return true;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
+ MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ mPlusClient.connect();
+ updateUI();
+ }
+
+ @Override
+ public void onStop() {
+ mPlusClient.disconnect();
+ mMediaRouter.removeCallback(mMediaRouterCallback);
+ super.onStop();
+ }
+
+ @Override
+ public void onDestroy() {
+ MediaRouteHelper.unregisterMediaRouteProvider(mCastContext);
+ mCastContext.dispose();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.sign_in_button) {
+ int available = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
+ if (available != ConnectionResult.SUCCESS) {
+ return;
+ }
+
+ try {
+ mConnectionResult.startResolutionForResult(this, REQUEST_CODE_SIGN_IN);
+ } catch (IntentSender.SendIntentException e) {
+ // Fetch a new result to start.
+ mPlusClient.connect();
+ }
+ }
+ }
+
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_SIGN_IN) {
+ if (resultCode == android.app.Activity.RESULT_OK && !mPlusClient.isConnected()
+ && !mPlusClient.isConnecting()) {
+ mPlusClient.connect();
+ }
+ }
+ }
+
+ @Override
+ public void onAccessRevoked(ConnectionResult status) {
+ if (status.isSuccess()) {
+ mConnected = false;
+ updateUI();
+ } else {
+ mPlusClient.disconnect();
+ }
+ mPlusClient.connect();
+ }
+
+ @Override
+ public void onConnected(Bundle bundle) {
+ mConnected = true;
+ Person me = mPlusClient.getCurrentPerson();
+ mUserId = me.getId();
+ mUserName = me.getDisplayName();
+ mUserAccount = mPlusClient.getAccountName();
+ updateAlbums();
+ updateUI();
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult result) {
+ mConnectionResult = result;
+ updateUI();
+ }
+
+ @Override
+ public void onDisconnected() {
+ mConnected = false;
+ updateUI();
+ mPlusClient.connect();
+ }
+
+ private void updateUI() {
+ MenuItem item;
+ if (mMenu != null) {
+ item = mMenu.findItem(R.id.action_signout);
+ if (item != null) item.setVisible(mConnected);
+ item = mMenu.findItem(R.id.action_revoke);
+ if (item != null) item.setVisible(mConnected);
+ }
+ if (mConnected && mUserName != null) {
+ mUserInfo.setText(" " + mUserName);
+ mUserInfo.setVisibility(View.VISIBLE);
+ } else {
+ mUserInfo.setVisibility(View.GONE);
+ }
+
+ mSignInButton.setVisibility(mConnected ? View.GONE : View.VISIBLE);
+ mContent.setVisibility(mConnected ? View.VISIBLE : View.GONE);
+ }
+
+ private void updateAlbums() {
+ if (!mConnected || mUserAccount == null) return;
+
+ PicasaAlbumsTask search =
+ new PicasaAlbumsTask(mReceiver,
+ this,
+ (String) mUserAccount,
+ (String) mUserId,
+ "oauth2:https://www.googleapis.com/auth/plus.login https://picasaweb.google.com/data/");
+ search.execute();
+ }
+
+ private class SearchReceiver extends AsyncReceiver {
+ @Override
+ public void finished(int results) {
+ if (results > 0) {
+ // refresh();
+ }
+ }
+ }
+
+ private class MyMediaRouterCallback extends MediaRouter.Callback {
+ @Override
+ public void onRouteSelected(MediaRouter router, RouteInfo route) {
+ MediaRouteHelper.requestCastDeviceForRoute(route);
+ }
+
+ @Override
+ public void onRouteUnselected(MediaRouter router, RouteInfo route) {
+ mSelectedDevice = null;
+ mRouteStateListener = null;
+ }
+ }
+
+ @Override
+ public void onDeviceAvailable(CastDevice device, String routeId,
+ MediaRouteStateChangeListener listener) {
+ mSelectedDevice = device;
+ mRouteStateListener = listener;
+ }
+
+ @Override
+ public void onSetVolume(double volume) {
+ // Handle volume change.
+ }
+
+ @Override
+ public void onUpdateVolume(double delta) {
+ // Handle volume change.
+ }
+}
diff --git a/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/PicasaAlbumsTask.java b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/PicasaAlbumsTask.java
new file mode 100644
index 0000000..50e58ff
--- /dev/null
+++ b/slideshow/android/SlideshowCast/src/at/foldedsoft/slideshowcast/PicasaAlbumsTask.java
@@ -0,0 +1,97 @@
+package at.foldedsoft.slideshowcast;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.net.ssl.HttpsURLConnection;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import com.google.android.gms.auth.GoogleAuthException;
+import com.google.android.gms.auth.GoogleAuthUtil;
+
+public class PicasaAlbumsTask extends AsyncTask {
+
+ AsyncReceiver receiver;
+ Context context;
+ String account;
+ String scopes;
+ String id;
+ //TrackerDBAdapter db;
+
+ PicasaAlbumsTask(AsyncReceiver receiver, /*TrackerDBAdapter db,*/ Context context, String account, String id, String scopes) {
+ this.receiver = receiver;
+ this.context = context;
+ this.account = account;
+ this.scopes = scopes;
+ this.id = id;
+ //this.db = db;
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ URL url;
+ HttpsURLConnection urlConnection;
+ InputStream in;
+ String result = "";
+ String line;
+ String accessToken = null;
+ try {
+ accessToken = GoogleAuthUtil.getToken(this.context, this.account, this.scopes);
+ } catch (GoogleAuthException e) {
+ e.printStackTrace();
+ return false;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ if (accessToken == null) return false;
+ try {
+ url = new URL("https://picasaweb.google.com/data/feed/api/user/default?alt=json&access=public&type=album");
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ try {
+ urlConnection = (HttpsURLConnection) url.openConnection();
+ urlConnection.setRequestProperty("Authorization", "Bearer " + accessToken);
+ in = new BufferedInputStream(urlConnection.getInputStream());
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ while((line = reader.readLine()) != null) {
+ result += " " + line;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ JSONObject json;
+ try {
+ json = new JSONObject(result);
+ if (json.has("feed")) {
+ json = json.optJSONObject("feed");
+ // TODO: handle feed
+ Log.d("picasa", json.toString());
+ return true;
+ } else {
+ return false;
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ protected void onPostExecute(Boolean result) {
+ if (result) {
+ receiver.finished(1);
+ }
+ }
+}