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