Merge "syncslides: UI fixes"
diff --git a/projects/syncslidepresenter/build.gradle b/projects/syncslidepresenter/build.gradle
new file mode 100644
index 0000000..e13c1f1
--- /dev/null
+++ b/projects/syncslidepresenter/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'application'
+apply plugin: 'java'
+apply plugin: 'io.v.vdl'
+
+buildscript {
+ repositories {
+ maven {
+ url 'https://maven.v.io'
+ }
+ }
+ dependencies {
+ classpath 'io.v:gradle-plugin:0.1-SNAPSHOT'
+ }
+}
+
+mainClassName = "io.v.syncslidepresenter"
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile project(':lib')
+ compile 'com.google.guava:guava:18'
+ compile 'com.beust:jcommander:1.48'
+}
+
+task copyLib(type: Copy) {
+ from([project(':lib').buildDir.getAbsolutePath(), 'libs', 'libv23.dylib'].join(File.separator))
+ from([project(':lib').buildDir.getAbsolutePath(), 'libs', 'libv23.so'].join(File.separator))
+ destinationDir = new File(['src', 'main', 'resources'].join(File.separator))
+}
+
+clean {
+ delete 'src/main/resources/libv23.so'
+ delete 'src/main/resources/libv23.dylib'
+}
+
+vdl {
+ inputPaths += [project(':projects:syncslides').projectDir.absolutePath + '/app/src/main/java']
+}
+
+sourceCompatibility = '1.8'
+targetCompatibility = '1.8'
\ No newline at end of file
diff --git a/projects/syncslidepresenter/src/main/java/io/v/syncslidepresenter/Main.java b/projects/syncslidepresenter/src/main/java/io/v/syncslidepresenter/Main.java
new file mode 100644
index 0000000..4f23fa5
--- /dev/null
+++ b/projects/syncslidepresenter/src/main/java/io/v/syncslidepresenter/Main.java
@@ -0,0 +1,304 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncslidepresenter;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import org.joda.time.Duration;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Window;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.UUID;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.imageio.ImageIO;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.WindowConstants;
+
+import io.v.android.apps.syncslides.db.VCurrentSlide;
+import io.v.android.apps.syncslides.db.VSlide;
+import io.v.impl.google.naming.NamingUtil;
+import io.v.impl.google.services.syncbase.SyncbaseServer;
+import io.v.v23.V;
+import io.v.v23.context.VContext;
+import io.v.v23.naming.Endpoint;
+import io.v.v23.rpc.Server;
+import io.v.v23.security.BlessingPattern;
+import io.v.v23.security.access.AccessList;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.syncbase.nosql.KeyValue;
+import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
+import io.v.v23.services.watch.ResumeMarker;
+import io.v.v23.syncbase.Syncbase;
+import io.v.v23.syncbase.SyncbaseApp;
+import io.v.v23.syncbase.SyncbaseService;
+import io.v.v23.syncbase.nosql.BatchDatabase;
+import io.v.v23.syncbase.nosql.Database;
+import io.v.v23.syncbase.nosql.RowRange;
+import io.v.v23.syncbase.nosql.Stream;
+import io.v.v23.syncbase.nosql.Syncgroup;
+import io.v.v23.syncbase.nosql.Table;
+import io.v.v23.syncbase.nosql.WatchChange;
+import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
+
+/**
+ * The entry point for syncslidepresenter.
+ */
+public class Main {
+ private static final Logger logger = Logger.getLogger(Main.class.getName());
+ private static final String SYNCBASE_APP = "syncslides";
+ private static final String SYNCBASE_DB = "syncslides";
+ private static final String PRESENTATIONS_TABLE = "Presentations";
+ private static final String DECKS_TABLE = "Decks";
+ private final Table presentations;
+ private final Table decks;
+ private final ImageViewer viewer;
+
+ private Database db;
+
+ private VContext context;
+
+ public static void main(String[] args) throws SyncbaseServer.StartException, VException, IOException {
+ Options options = new Options();
+ JCommander commander = new JCommander(options);
+ try {
+ commander.parse(args);
+ } catch (ParameterException e) {
+ logger.warning("Could not parse parameters: " + e.getMessage());
+ commander.usage();
+ return;
+ }
+
+ if (options.help) {
+ commander.usage();
+ return;
+ }
+
+ // Make command-Q do the same as closing the main frame (i.e. exit).
+ System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
+
+ JFrame frame = new JFrame();
+ enableOSXFullscreen(frame);
+ frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+
+ VContext baseContext = V.init();
+
+ AccessList acl = new AccessList(ImmutableList.of(new BlessingPattern("...")),
+ ImmutableList.<String>of());
+ Permissions permissions = new Permissions(ImmutableMap.of("1", acl));
+ String name = NamingUtil.join(options.mountPrefix, UUID.randomUUID().toString());
+ logger.info("Mounting new syncbase server at " + name);
+ baseContext = SyncbaseServer.withNewServer(
+ baseContext.withTimeout(Duration.standardSeconds(options.mountTimeoutSeconds)),
+ new SyncbaseServer.Params().withPermissions(permissions).withName(name));
+ final Server server = V.getServer(baseContext);
+ if (server.getStatus().getEndpoints().length > 0) {
+ logger.info("Mounted syncbase server at the following endpoints: ");
+ for (Endpoint e : server.getStatus().getEndpoints()) {
+ logger.info("\t" + e);
+ }
+ logger.info("End of endpoint list");
+
+ SyncbaseService service
+ = Syncbase.newService("/" + server.getStatus().getEndpoints()[0]);
+ SyncbaseApp app = service.getApp(SYNCBASE_APP);
+ if (!app.exists(baseContext)) {
+ app.create(baseContext, permissions);
+ }
+ Database db = app.getNoSqlDatabase(SYNCBASE_DB, null);
+ if (!db.exists(baseContext)) {
+ db.create(baseContext, permissions);
+ }
+ Table decks = db.getTable(DECKS_TABLE);
+ if (!decks.exists(baseContext)) {
+ decks.create(baseContext, permissions);
+ }
+ Table presentations = db.getTable(PRESENTATIONS_TABLE);
+ if (!presentations.exists(baseContext)) {
+ presentations.create(baseContext, permissions);
+ }
+
+ JPanel panel = new JPanel(new GridBagLayout());
+ ScaleToFitJPanel presentationPanel = new ScaleToFitJPanel();
+ GridBagConstraints constraints = new GridBagConstraints();
+ constraints.weightx = 1;
+ constraints.weighty = 1;
+ constraints.fill = GridBagConstraints.BOTH;
+ panel.add(presentationPanel, constraints);
+ frame.getContentPane().add(panel);
+ frame.pack();
+
+ Main m = new Main(baseContext, presentationPanel, db, decks, presentations);
+ m.joinPresentation(options.presentationName, options.joinTimeoutSeconds,
+ options.currentSlideKey, options.slideRowFormat);
+ }
+ }
+
+ public Main(VContext context, ImageViewer viewer, Database db, Table decks,
+ Table presentations) throws VException {
+ this.context = context;
+ this.db = db;
+ this.presentations = presentations;
+ this.decks = decks;
+ this.viewer = viewer;
+ }
+
+ public void joinPresentation(final String syncgroupName,
+ int joinTimeoutSeconds,
+ String currentSlideKey,
+ String slideRowFormat) throws VException {
+ Syncgroup syncgroup = db.getSyncgroup(syncgroupName);
+ syncgroup.join(context.withTimeout(Duration.standardSeconds(joinTimeoutSeconds)),
+ new SyncgroupMemberInfo((byte) 1));
+ for (String member : syncgroup.getMembers(context).keySet()) {
+ logger.info("Member: " + member);
+ }
+
+ for (KeyValue keyValue : presentations.scan(context, RowRange.prefix(""))) {
+ System.out.println("Presentation: " + keyValue);
+ }
+ BatchDatabase batch = db.beginBatch(context, null);
+ ResumeMarker marker = batch.getResumeMarker(context);
+ Stream<WatchChange> watchStream = db.watch(context, presentations.name(), currentSlideKey,
+ marker);
+
+ for (WatchChange w : watchStream) {
+ logger.info("Change detected in " + w.getRowName());
+ logger.info("Type: " + w.getChangeType());
+ try {
+ VCurrentSlide currentSlide = (VCurrentSlide) VomUtil.decode(w.getVomValue(),
+ VCurrentSlide.class);
+ logger.info("Current slide: " + currentSlide);
+ // Read the corresponding slide.
+ VSlide slide = (VSlide) decks.getRow(String.format(slideRowFormat,
+ currentSlide.getNum())).get(context, VSlide.class);
+ final BufferedImage image = ImageIO.read(
+ new ByteArrayInputStream(slide.getThumbnail()));
+ viewer.setImage(image);
+ } catch (IOException | VException e) {
+ logger.log(Level.WARNING, "exception encountered while handling change event", e);
+ }
+ }
+ }
+
+ private static void enableOSXFullscreen(Window window) {
+ Preconditions.checkNotNull(window);
+ try {
+ // This class may not be present on the system (e.g. if we're not on MacOSX),
+ // use reflection so that we can make this an optional dependency.
+ Class util = Class.forName("com.apple.eawt.FullScreenUtilities");
+ Class params[] = new Class[]{Window.class, Boolean.TYPE};
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ Method method = util.getMethod("setWindowCanFullScreen", params);
+ method.invoke(util, window, true);
+ } catch (ClassNotFoundException e) {
+ // Probably not on Mac OS X
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Couldn't enable fullscreen on Mac OS X", e);
+ }
+ }
+
+ public static class Options {
+ @Parameter(names = {"-m", "--mountPrefix"}, description = "the base path in the namespace"
+ + " where the syncbase service will be mounted")
+ private String mountPrefix = "/192.168.86.254:8101";
+
+ @Parameter(names = {"-p", "--presentationName"},
+ description = "the presentation to watch")
+ private String presentationName = "/192.168.86.254:8101/990005300000537/%%sync/"
+ + "syncslides/deckId1/randomPresentationId1";
+
+ @Parameter(names = {"--mountTimeout"},
+ description = "the number of seconds to wait for the syncbase server to be created")
+ private int mountTimeoutSeconds = 10;
+
+ @Parameter(names = {"--joinTimeout"},
+ description = "the number of seconds to wait to join the presentation")
+ private int joinTimeoutSeconds = 10;
+
+ @Parameter(names = {"-k", "--currentSlideKey"},
+ description = "the row key containing the current slide")
+ private String currentSlideKey = "deckId1/randomPresentationId1/CurrentSlide";
+
+ @Parameter(names = {"-f", "--slideRowFormat"},
+ description = "a pattern specifying where slide rows are found")
+ private String slideRowFormat = "deckId1/slides/%04d";
+
+ @Parameter(names = {"-h", "--help"}, description = "display this help message", help = true)
+ private boolean help = false;
+ }
+
+ private static class ScaleToFitJPanel extends JPanel implements ImageViewer {
+ private Image image;
+
+ public ScaleToFitJPanel() {
+ super();
+ setPreferredSize(new Dimension(250, 250));
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) {
+ repaint();
+ }
+ });
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+
+ if (image != null) {
+ int width;
+ int height;
+ double containerRatio = 1.0d * getWidth() / getHeight();
+ double imageRatio = 1.0d * image.getWidth(null) / image.getHeight(null);
+
+ if (containerRatio < imageRatio) {
+ width = getWidth();
+ height = (int) (getWidth() / imageRatio);
+ } else {
+ width = (int) (getHeight() * imageRatio);
+ height = getHeight();
+ }
+
+ // Center the image in the container.
+ int x = (int) (((double) getWidth() / 2) - ((double) width / 2));
+ int y = (int) (((double) getHeight()/ 2) - ((double) height / 2));
+
+ g.drawImage(image, x, y, width, height, this);
+ }
+ }
+
+ @Override
+ public void setImage(Image image) {
+ this.image = image;
+ setPreferredSize(new Dimension(image.getWidth(null), image.getHeight(null)));
+ repaint();
+ }
+ }
+
+ private interface ImageViewer {
+ void setImage(Image image);
+ }
+}
diff --git a/projects/syncslides/app/build.gradle b/projects/syncslides/app/build.gradle
index 81966f8..c8c0063 100644
--- a/projects/syncslides/app/build.gradle
+++ b/projects/syncslides/app/build.gradle
@@ -48,7 +48,7 @@
defaultConfig {
applicationId "io.v.android.apps.syncslides"
- minSdkVersion 21
+ minSdkVersion 22
targetSdkVersion 23
versionCode 1
versionName "1.0"
diff --git a/projects/syncslides/app/src/main/AndroidManifest.xml b/projects/syncslides/app/src/main/AndroidManifest.xml
index 1719535..6030afe 100644
--- a/projects/syncslides/app/src/main/AndroidManifest.xml
+++ b/projects/syncslides/app/src/main/AndroidManifest.xml
@@ -1,29 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest package="io.v.android.apps.syncslides"
- xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="io.v.android.apps.syncslides" >
+
+ <uses-sdk android:minSdkVersion="22" />
+
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:theme="@style/AppTheme">
+ android:theme="@style/AppTheme" >
<activity
- android:name=".DeckChooserActivity"
- android:label="@string/app_name">
+ android:name=".SignInActivity"
+ android:label="@string/app_name" >
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
-
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name=".PresentationActivity"/>
+ <activity
+ android:name=".DeckChooserActivity"
+ android:label="@string/app_name" >
+ </activity>
+ <activity android:name=".PresentationActivity" />
<service
android:name=".discovery.ParticipantPeer"
android:exported="false"
android:label="Location Service"
- android:process=":ParticipantPeer">
+ android:process=":ParticipantPeer" >
</service>
</application>
- <uses-permission android:name="android.permission.READ_PHONE_STATE" />
</manifest>
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
index 51fdd89..75fbe0b 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/DeckListAdapter.java
@@ -87,13 +87,13 @@
}
@Override
- public void onBindViewHolder(final ViewHolder holder, int i) {
+ public void onBindViewHolder(final ViewHolder holder, int deckIndex) {
final Deck deck;
final Role role;
// If the position is less than the number of live presentation decks, get deck card from
// there (and don't allow the user to delete the deck). If not, get the card from the DB.
- if (i < mLiveDecks.getItemCount()) {
- deck = mLiveDecks.get(i);
+ if (deckIndex < mLiveDecks.getItemCount()) {
+ deck = mLiveDecks.get(deckIndex);
holder.mToolbarLiveNow
.setText(holder.itemView.getResources().getString(R.string.presentation_live));
holder.mToolbarLiveNow.setVisibility(View.VISIBLE);
@@ -101,7 +101,7 @@
holder.mToolbar.getMenu().clear();
role = Role.AUDIENCE;
} else {
- deck = mDecks.get(i - mLiveDecks.getItemCount());
+ deck = mDecks.get(deckIndex - mLiveDecks.getItemCount());
// TODO(afergan): Set actual date here.
holder.mToolbarLastOpened.setText("Opened on Oct 26, 2015");
holder.mToolbarLastOpened.setVisibility(View.VISIBLE);
@@ -141,24 +141,23 @@
new DB.Callback<Void>() {
@Override
public void done(Void aVoid) {
- // Intent for the activity to open when user selects the thumbnail.
- Intent intent = new Intent(context, PresentationActivity.class);
- intent.putExtras(deck.toBundle(null));
- intent.putExtra(Participant.B.PARTICIPANT_ROLE, role);
- context.startActivity(intent);
+ showSlides(context, deck, role);
}
});
} else {
- // Intent for the activity to open when user selects the thumbnail.
- Intent intent = new Intent(context, PresentationActivity.class);
- intent.putExtras(deck.toBundle(null));
- intent.putExtra(Participant.B.PARTICIPANT_ROLE, role);
- context.startActivity(intent);
+ showSlides(context, deck, role);
}
}
});
}
+ private void showSlides(Context context, Deck deck, Role role) {
+ Intent intent = new Intent(context, PresentationActivity.class);
+ intent.putExtra(Deck.B.DECK_ID, deck.getId());
+ intent.putExtra(Participant.B.PARTICIPANT_ROLE, role);
+ context.startActivity(intent);
+ }
+
@Override
public int getItemCount() {
return mLiveDecks.getItemCount() + mDecks.getItemCount();
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/NavigationDrawerFragment.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/NavigationDrawerFragment.java
index 61fc9b4..2f5d437 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/NavigationDrawerFragment.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/NavigationDrawerFragment.java
@@ -15,6 +15,7 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -24,6 +25,10 @@
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.TextView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
@@ -31,6 +36,7 @@
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
public class NavigationDrawerFragment extends Fragment {
+ private static final String TAG = "NavigationDrawer";
/**
* Remember the position of the selected item.
@@ -56,6 +62,7 @@
private DrawerLayout mDrawerLayout;
private ListView mDrawerListView;
private View mFragmentContainerView;
+ private JSONObject mUserProfile;
private int mCurrentSelectedPosition = 0;
private boolean mFromSavedInstanceState;
@@ -70,8 +77,14 @@
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
- mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ mUserLearnedDrawer = prefs.getBoolean(PREF_USER_LEARNED_DRAWER, false);
+ String userProfileJsonStr = prefs.getString(SignInActivity.PREF_USER_PROFILE_JSON, "");
+ try {
+ mUserProfile = new JSONObject(userProfileJsonStr);
+ } catch (JSONException e) {
+ Log.e(TAG, "Couldn't parse user profile data: " + userProfileJsonStr);
+ }
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
@@ -100,15 +113,33 @@
selectItem(position);
}
});
- mDrawerListView.setAdapter(new ArrayAdapter<String>(
+ mDrawerListView.setAdapter(new ArrayAdapter<JSONObject>(
getActionBar().getThemedContext(),
- android.R.layout.simple_list_item_activated_1,
+ android.R.layout.simple_list_item_activated_2,
android.R.id.text1,
- new String[]{
- getString(R.string.title_account1),
- getString(R.string.title_account2),
- getString(R.string.title_account3),
- }));
+ new JSONObject[]{mUserProfile}) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ JSONObject userProfile = getItem(position);
+ String name = "";
+ String email = "";
+ if (userProfile != null) {
+ try {
+ name = userProfile.getString("name");
+ email = userProfile.getString("email");
+ } catch (JSONException e) {
+ Log.e(TAG, "Error reading from user profile: " + e.getMessage());
+ }
+ }
+ if (name.isEmpty() && email.isEmpty()) {
+ name = "USER1";
+ }
+ View view = super.getView(position, convertView, parent);
+ ((TextView) view.findViewById(android.R.id.text1)).setText(name);
+ ((TextView) view.findViewById(android.R.id.text2)).setText(email);
+ return view;
+ }
+ });
mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return mDrawerListView;
}
@@ -269,7 +300,7 @@
/**
* Callbacks interface that all activities using this fragment must implement.
*/
- public static interface NavigationDrawerCallbacks {
+ public interface NavigationDrawerCallbacks {
/**
* Called when an item in the navigation drawer is selected.
*/
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
index 8943ccd..9ff1fa9 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationActivity.java
@@ -13,12 +13,11 @@
import android.widget.Toast;
import io.v.android.apps.syncslides.db.DB;
-import io.v.android.apps.syncslides.discovery.ParticipantServerImpl;
+import io.v.android.apps.syncslides.discovery.ParticipantPeer;
import io.v.android.apps.syncslides.misc.Config;
import io.v.android.apps.syncslides.misc.V23Manager;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckFactory;
-import io.v.android.apps.syncslides.model.DeckImpl;
import io.v.android.apps.syncslides.model.Participant;
import io.v.android.apps.syncslides.model.Role;
@@ -62,16 +61,17 @@
mShouldBeAdvertising = false;
mIsAdvertising = false;
+ String deckId;
if (savedInstanceState == null) {
Log.d(TAG, "savedInstanceState is null");
- mDeck = DeckImpl.fromBundle(getIntent().getExtras());
+ deckId = getIntent().getStringExtra(Deck.B.DECK_ID);
mRole = (Role) getIntent().getSerializableExtra(
Participant.B.PARTICIPANT_ROLE);
mSynced = true;
} else {
Log.d(TAG, "savedInstanceState is NOT null");
- mDeck = DeckImpl.fromBundle(savedInstanceState);
mRole = (Role) savedInstanceState.get(Participant.B.PARTICIPANT_ROLE);
+ deckId = savedInstanceState.getString(Deck.B.DECK_ID);
mSynced = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SYNCED);
mShouldBeAdvertising = savedInstanceState.getBoolean(Participant.B.PARTICIPANT_SHOULD_ADV);
if (mShouldBeAdvertising) {
@@ -79,6 +79,13 @@
}
}
+ Log.d(TAG, "Role = " + mRole);
+ mDeck = DB.Singleton.get(getApplicationContext()).getDeck(deckId);
+ if (mDeck == null) {
+ throw new IllegalArgumentException("Unusable deckId: "+ deckId);
+ }
+ Log.d(TAG, "Using deck: " + mDeck);
+
// TODO(jregan): This appears to be an attempt to avoid fragment
// re-inflation, possibly the right thing to do is move the code
// below to another flow step, e.g. onRestoreInstanceState.
@@ -114,10 +121,10 @@
@Override
protected void onSaveInstanceState(Bundle b) {
- Log.d(TAG, "onSaveInstanceState");
super.onSaveInstanceState(b);
- mDeck.toBundle(b);
+ Log.d(TAG, "onSaveInstanceState");
b.putSerializable(Participant.B.PARTICIPANT_ROLE, mRole);
+ b.putString(Deck.B.DECK_ID, mDeck.getId());
b.putBoolean(Participant.B.PARTICIPANT_SYNCED, mSynced);
b.putBoolean(Participant.B.PARTICIPANT_SHOULD_ADV, mShouldBeAdvertising);
}
@@ -175,7 +182,7 @@
if (shouldUseV23()) {
V23Manager.Singleton.get().mount(
Config.MtDiscovery.makeMountName(mDeck),
- new ParticipantServerImpl(mDeck));
+ new ParticipantPeer.Server(mDeck));
Log.d(TAG, "MT advertising started.");
} else {
Log.d(TAG, "No means to start advertising.");
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/SignInActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/SignInActivity.java
new file mode 100644
index 0000000..3d225bc
--- /dev/null
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/SignInActivity.java
@@ -0,0 +1,234 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.android.apps.syncslides;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerCallback;
+import android.accounts.AccountManagerFuture;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Signs in the user into one of his Gmail accounts.
+ */
+public class SignInActivity extends AppCompatActivity {
+ private static final String TAG = "SignInActivity";
+
+ public static final String PREF_USER_ACCOUNT_NAME = "user_account";
+ public static final String PREF_USER_PROFILE_JSON = "user_profile";
+
+ private static final int REQUEST_CODE_PICK_ACCOUNT = 1000;
+ private static final int REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL = 1001;
+
+ private static final String OAUTH_PROFILE = "email";
+ private static final String OAUTH_SCOPE = "oauth2:" + OAUTH_PROFILE;
+ private static final String OAUTH_USERINFO_URL =
+ "https://www.googleapis.com/oauth2/v2/userinfo";
+
+ private SharedPreferences mPrefs;
+ private String mAccountName;
+ private ProgressDialog mProgressDialog;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_sign_in);
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mAccountName = mPrefs.getString(SignInActivity.PREF_USER_ACCOUNT_NAME, "");
+ mProgressDialog = new ProgressDialog(this);
+ if (mAccountName.isEmpty()) {
+ mProgressDialog.setMessage("Signing in...");
+ mProgressDialog.show();
+ pickAccount();
+ } else {
+ finishActivity();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CODE_PICK_ACCOUNT: {
+ if (resultCode != RESULT_OK) {
+ Toast.makeText(this, "Must pick account", Toast.LENGTH_LONG).show();
+ pickAccount();
+ break;
+ }
+ pickAccountDone(data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME));
+ break;
+ }
+ case REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL:
+ if (resultCode != RESULT_OK) {
+ Log.e(TAG, "User didn't approve oauth2 request");
+ break;
+ }
+ fetchUserProfile();
+ break;
+ }
+ }
+
+ private void pickAccount() {
+ Intent chooseIntent = AccountManager.newChooseAccountIntent(
+ null, null, new String[]{"com.google"}, false, null, null, null, null);
+ startActivityForResult(chooseIntent, REQUEST_CODE_PICK_ACCOUNT);
+ }
+
+ private void pickAccountDone(String accountName) {
+ mAccountName = accountName;
+ SharedPreferences.Editor editor = mPrefs.edit();
+ editor.putString(PREF_USER_ACCOUNT_NAME, accountName);
+ editor.commit();
+
+ fetchUserProfile();
+ }
+
+ private void fetchUserProfile() {
+ AccountManager manager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
+ Account[] accounts = manager.getAccountsByType("com.google");
+ Account account = null;
+ for (int i = 0; i < accounts.length; i++) {
+ if (accounts[i].name.equals(mAccountName)) {
+ account = accounts[i];
+ break;
+ }
+ }
+ if (account == null) {
+ Log.e(TAG, "Couldn't find Google account with name: " + mAccountName);
+ pickAccount();
+ return;
+ }
+ manager.getAuthToken(account,
+ OAUTH_SCOPE,
+ new Bundle(),
+ false,
+ new OnTokenAcquired(),
+ new Handler(new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ Log.e(TAG, "Error getting auth token: " + msg.toString());
+ fetchUserProfileDone(null);
+ return true;
+ }
+ }));
+ }
+
+ private void fetchUserProfileDone(JSONObject userProfile) {
+ if (userProfile != null) {
+ SharedPreferences.Editor editor = mPrefs.edit();
+ editor.putString(PREF_USER_PROFILE_JSON, userProfile.toString());
+ editor.commit();
+ }
+
+ finishActivity();
+ }
+
+
+ private void finishActivity() {
+ mProgressDialog.dismiss();
+ startActivity(new Intent(this, DeckChooserActivity.class));
+ finish();
+ }
+
+ private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
+ @Override
+ public void run(AccountManagerFuture<Bundle> result) {
+ try {
+ Bundle bundle = result.getResult();
+ Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
+ if (launch != null) { // Needs user approval.
+ // NOTE(spetrovic): The returned intent has the wrong flag value
+ // FLAG_ACTIVITY_NEW_TASK set, which results in the launched intent replying
+ // immediately with RESULT_CANCELED. Hence, we clear the flag here.
+ launch.setFlags(0);
+ startActivityForResult(launch, REQUEST_CODE_FETCH_USER_PROFILE_APPROVAL);
+ return;
+ }
+ String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
+ new ProfileInfoFetcher().execute(token);
+ } catch (AuthenticatorException e) {
+ Log.e(TAG, "Couldn't authorize: " + e.getMessage());
+ fetchUserProfileDone(null);
+ } catch (OperationCanceledException e) {
+ Log.e(TAG, "Authorization cancelled: " + e.getMessage());
+ fetchUserProfileDone(null);
+ } catch (IOException e) {
+ Log.e(TAG, "Unexpected error: " + e.getMessage());
+ fetchUserProfileDone(null);
+ }
+ }
+ }
+
+ private class ProfileInfoFetcher extends AsyncTask<String, Void, JSONObject> {
+ @Override
+ protected JSONObject doInBackground(String... params) {
+ try {
+ URL url = new URL(OAUTH_USERINFO_URL + "?access_token=" + params[0]);
+ return new JSONObject(CharStreams.toString(
+ new InputStreamReader(url.openConnection().getInputStream(),
+ Charsets.US_ASCII)));
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Error fetching user's profile info" + e.getMessage());
+ } catch (JSONException e) {
+ Log.e(TAG, "Error fetching user's profile info" + e.getMessage());
+ } catch (IOException e) {
+ Log.e(TAG, "Error fetching user's profile info" + e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(JSONObject userProfile) {
+ fetchUserProfileDone(userProfile);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_sign_in, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
index b331bb6..668b7e0 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/DB.java
@@ -126,6 +126,13 @@
void importDeck(Deck deck, Slide[] slides, Callback<Void> callback);
/**
+ * Synchronously gets a deck by Id. Returns null if not found.
+ *
+ * @param deckId id of the deck to get
+ */
+ Deck getDeck(String deckId);
+
+ /**
* Asynchronously deletes the deck and all of its slides.
*
* @param deckId id of the deck to delete
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
index 1df52f2..cd41a7d 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/FakeDB.java
@@ -326,6 +326,17 @@
}
@Override
+ public Deck getDeck(String deckId) {
+ for (int i = 0; i < mDecks.getItemCount(); i++) {
+ Deck result = mDecks.get(i);
+ if (result.getId().equals(deckId)) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ @Override
public void deleteDeck(String deckId) {
mDecks.delete(deckId);
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
index dcea924..32341af 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/db/SyncbaseDB.java
@@ -281,6 +281,7 @@
@Override
public void run() {
try {
+ Log.i(TAG, "Joining: " + syncgroupName);
Syncgroup syncgroup = mDB.getSyncgroup(syncgroupName);
syncgroup.join(mVContext, new SyncgroupMemberInfo((byte) 1));
for (String member : syncgroup.getMembers(mVContext).keySet()) {
@@ -309,15 +310,15 @@
return new DeckList(mVContext, mDB, mDeckFactory);
}
- private static class DeckList implements DBList {
+ private static class DeckList implements DBList<Deck> {
private final CancelableVContext mVContext;
private final Database mDB;
private final DeckFactory mDeckFactory;
private final Handler mHandler;
private ResumeMarker mWatchMarker;
- private volatile boolean mIsDiscarded;
- private volatile Listener mListener;
+ private boolean mIsDiscarded;
+ private Listener mListener;
private List<Deck> mDecks;
public DeckList(VContext vContext, Database db, DeckFactory df) {
@@ -434,18 +435,20 @@
}
@Override
- public synchronized void discard() {
+ public void discard() {
Log.i(TAG, "Discarding deck list.");
+ mIsDiscarded = true;
mVContext.cancel(); // this will cause the watcher thread to exit
mHandler.removeCallbacksAndMessages(null);
- // We've canceled all the pending callbacks, but the handler might be just about
- // to execute put()/get() and those messages wouldn't get canceled. So we mark
- // the list as discarded and count on put()/get() checking for it. (Note that
- // put()/get() are synchronized along with this method.)
- mIsDiscarded = true;
}
- private synchronized void put(Deck deck) {
+ private void put(Deck deck) {
+ // We need to check for mIsDiscarded (even though removeCallbacksAndMessages() has
+ // been called), because that method doesn't prevent future post()s being made on
+ // the handler. So the following scenario is possible:
+ // - fetcher thread is about to execute post().
+ // - discard clears all pending messages from the handler.
+ // - fetcher executes the post().
if (mIsDiscarded) {
return;
}
@@ -472,7 +475,13 @@
}
}
- private synchronized void delete(String deckId) {
+ private void delete(String deckId) {
+ // We need to check for mIsDiscarded (even though removeCallbacksAndMessages() has
+ // been called), because that method doesn't prevent future post()s being made on
+ // the handler. So the following scenario is possible:
+ // - fetcher thread is about to execute post().
+ // - discard clears all pending messages from the handler.
+ // - fetcher executes the post().
if (mIsDiscarded) {
return;
}
@@ -547,7 +556,7 @@
private final Handler mHandler;
private final String mDeckId;
private ResumeMarker mWatchMarker;
- private volatile boolean mIsDiscarded;
+ private boolean mIsDiscarded;
// Storage for slides, mirroring the slides in the Syncbase. Since slide numbers can
// have "holes" in them (e.g., 1, 2, 4, 6, 8), we maintain a map from slide key
// to the slide, as well as an ordered list which is returned to the caller.
@@ -679,18 +688,20 @@
}
@Override
- public synchronized void discard() {
+ public void discard() {
Log.i(TAG, "Discarding slides list");
+ mIsDiscarded = true;
mVContext.cancel(); // this will cause the watcher thread to exit
mHandler.removeCallbacksAndMessages(null);
- // We've canceled all the pending callbacks, but the handler might be just about
- // to execute put()/get() and those messages wouldn't get canceled. So we mark
- // the list as discarded and count on put()/get() checking for it. (Note that
- // put()/get() are synchronized along with this method.)
- mIsDiscarded = true;
}
- private synchronized void put(String key, Slide slide) {
+ private void put(String key, Slide slide) {
+ // We need to check for mIsDiscarded (even though removeCallbacksAndMessages() has
+ // been called), because that method doesn't prevent future post()s being made on
+ // the handler. So the following scenario is possible:
+ // - fetcher thread is about to execute post().
+ // - discard clears all pending messages from the handler.
+ // - fetcher executes the post().
if (mIsDiscarded) {
return;
}
@@ -710,7 +721,13 @@
}
}
- private synchronized void delete(String key) {
+ private void delete(String key) {
+ // We need to check for mIsDiscarded (even though removeCallbacksAndMessages() has
+ // been called), because that method doesn't prevent future post()s being made on
+ // the handler. So the following scenario is possible:
+ // - fetcher thread is about to execute post().
+ // - discard clears all pending messages from the handler.
+ // - fetcher executes the post().
if (mIsDiscarded) {
return;
}
@@ -944,6 +961,20 @@
}
@Override
+ public Deck getDeck(String deckId) {
+ VDeck vDeck = null;
+ try {
+ vDeck = (VDeck) mDecks.get(mVContext, deckId, VDeck.class);
+ } catch (VException e) {
+ handleError(e.toString());
+ }
+ if (vDeck != null) {
+ return mDeckFactory.make(vDeck, deckId);
+ }
+ return null;
+ }
+
+ @Override
public void deleteDeck(String deckId) {
try {
mDecks.deleteRange(mVContext, RowRange.prefix(deckId));
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
index 2fedf4b..5454690 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantPeer.java
@@ -4,17 +4,22 @@
package io.v.android.apps.syncslides.discovery;
+import android.graphics.Bitmap;
import android.util.Log;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
+import java.io.ByteArrayOutputStream;
+
import io.v.android.apps.syncslides.db.VDeck;
import io.v.android.apps.syncslides.misc.V23Manager;
import io.v.android.apps.syncslides.model.Deck;
import io.v.android.apps.syncslides.model.DeckFactory;
import io.v.android.apps.syncslides.model.Participant;
+import io.v.v23.context.VContext;
+import io.v.v23.rpc.ServerCall;
import io.v.v23.verror.VException;
/**
@@ -149,4 +154,33 @@
static final String SERVER_NAME = "unknownServerName";
static final String USER_NAME = "unknownUserName";
}
+
+ /**
+ * Serves data used in deck discovery.
+ */
+ public static class Server implements ParticipantServer {
+ private static final String TAG = "ParticipantServer";
+ private final Deck mDeck;
+
+ public Server(Deck d) {
+ mDeck = d;
+ }
+
+ public VDeck get(VContext ctx, ServerCall call)
+ throws VException {
+ Log.d(TAG, "Responding to Get RPC.");
+ Log.d(TAG, " Sending mDeck = " + mDeck);
+ VDeck d = new VDeck();
+ d.setTitle(mDeck.getTitle());
+ if (mDeck.getThumb() == null) {
+ Log.d(TAG, " The response deck has no thumb.");
+ } else {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ Bitmap bitmap = mDeck.getThumb();
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream);
+ d.setThumbnail(stream.toByteArray());
+ }
+ return d;
+ }
+ }
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
deleted file mode 100644
index 2e5a83c..0000000
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/discovery/ParticipantServerImpl.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.android.apps.syncslides.discovery;
-
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import java.io.ByteArrayOutputStream;
-
-import io.v.android.apps.syncslides.db.VDeck;
-import io.v.android.apps.syncslides.model.Deck;
-import io.v.v23.context.VContext;
-import io.v.v23.rpc.ServerCall;
-
-/**
- * Serves data used in deck discovery.
- */
-public class ParticipantServerImpl implements ParticipantServer {
- private static final String TAG = "PresentationActivity";
- private final Deck mDeck;
-
- public ParticipantServerImpl(Deck d) {
- mDeck = d;
- }
-
- public VDeck get(VContext ctx, ServerCall call)
- throws io.v.v23.verror.VException {
- Log.d(TAG, "Responding to Get RPC.");
- Log.d(TAG, " Sending mDeck = " + mDeck);
- VDeck d = new VDeck();
- d.setTitle(mDeck.getTitle());
- if (mDeck.getThumb() == null) {
- Log.d(TAG, " The response deck has no thumb.");
- } else {
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- Bitmap bitmap = mDeck.getThumb();
- bitmap.compress(Bitmap.CompressFormat.JPEG, 60, stream);
- d.setThumbnail(stream.toByteArray());
- }
- return d;
- }
-}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/misc/V23Manager.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/misc/V23Manager.java
index 5bc8a95..734d79e 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/misc/V23Manager.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/misc/V23Manager.java
@@ -7,6 +7,8 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
@@ -20,6 +22,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import io.v.android.apps.syncslides.SignInActivity;
import io.v.android.libs.security.BlessingsManager;
import io.v.android.v23.V;
import io.v.android.v23.services.blessing.BlessingCreationException;
@@ -172,8 +175,11 @@
throw new IllegalArgumentException(
"Cannot get blessings without an activity to return to.");
}
+ // Get the signed-in user's email to generate the blessings from.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(androidCtx);
+ String userEmail = prefs.getString(SignInActivity.PREF_USER_ACCOUNT_NAME, "");
activity.startActivityForResult(
- BlessingService.newBlessingIntent(androidCtx),
+ BlessingService.newBlessingIntent(androidCtx, userEmail),
BLESSING_REQUEST);
return;
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Deck.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Deck.java
index adf2ec0..642894a 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Deck.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Deck.java
@@ -28,17 +28,9 @@
String getId();
/**
- * Returns a bundled form of the instance; pass null for a new bundle,
- * pass an existing bundle to overwrite its fields.
- */
- Bundle toBundle(Bundle b);
-
- /**
- * Keys for Bundle fields.
+ * Keys for Bundle/Intent fields.
*/
class B {
public static final String DECK_ID = "deck_id";
- public static final String DECK_TITLE = "deck_title";
- public static final String DECK_THUMB = "deck_thumb";
}
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
index 07e0d31..d5993b4 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/DeckImpl.java
@@ -42,30 +42,6 @@
return mDeckId.hashCode();
}
- public static Deck fromBundle(Bundle b) {
- if (b == null) {
- throw new IllegalArgumentException("Need a bundle.");
- }
- return new DeckImpl(
- b.getString(B.DECK_TITLE),
- (Bitmap) b.getParcelable(B.DECK_THUMB),
- b.getString(B.DECK_ID));
- }
-
- @Override
- public Bundle toBundle(Bundle b) {
- if (b == null) {
- b = new Bundle();
- }
- b.putString(B.DECK_TITLE, mTitle);
- // TODO(jregan): Our thumbnails are too big for intent use.
- // Could store on disk, pass a file handle in the intent instead,
- // and load them on the other side.
- // ### b.putParcelable(B.DECK_THUMB, mThumb);
- b.putString(B.DECK_ID, mDeckId);
- return b;
- }
-
@Override
public Bitmap getThumb() {
return mThumb;
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
index 90fbadf..fe83e5c 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/model/Participant.java
@@ -42,9 +42,6 @@
class B {
public static final String PARTICIPANT_ROLE = "participant_role";
public static final String PARTICIPANT_SHOULD_ADV = "participant_is_advertising";
- public static final String PARTICIPANT_SERVICE_NAME = "participant_endPoint";
- public static final String PARTICIPANT_NAME = "participant_name";
- public static final String PARTICIPANT_BLESSINGS = "participant_blessings";
public static final String PARTICIPANT_SYNCED = "participant_synced";
}
}
diff --git a/projects/syncslides/app/src/main/res/layout/activity_sign_in.xml b/projects/syncslides/app/src/main/res/layout/activity_sign_in.xml
new file mode 100644
index 0000000..7c93107
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/layout/activity_sign_in.xml
@@ -0,0 +1,9 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+ android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ tools:context="io.v.android.apps.syncslides.SignInActivity">
+
+</RelativeLayout>
diff --git a/projects/syncslides/app/src/main/res/menu/menu_sign_in.xml b/projects/syncslides/app/src/main/res/menu/menu_sign_in.xml
new file mode 100644
index 0000000..813fc46
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/menu/menu_sign_in.xml
@@ -0,0 +1,7 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="io.v.android.apps.syncslides.SignInActivity">
+ <item android:id="@+id/action_settings" android:title="@string/action_settings"
+ android:orderInCategory="100" app:showAsAction="never" />
+</menu>
diff --git a/projects/syncslides/app/src/main/res/values/strings.xml b/projects/syncslides/app/src/main/res/values/strings.xml
index 7ffb985..48013cd 100644
--- a/projects/syncslides/app/src/main/res/values/strings.xml
+++ b/projects/syncslides/app/src/main/res/values/strings.xml
@@ -22,5 +22,4 @@
<string name="handoff_message">You handed off to</string>
<string name="end_handoff">RESUME</string>
<string name="presentation_live">LIVE NOW</string>
-
</resources>
diff --git a/settings.gradle b/settings.gradle
index b511423..d9d2af5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include 'lib', 'tests', 'android-lib', 'benchmarks:syncbench', 'projects:syncslides', 'projects:syncslides:app'
+include 'lib', 'tests', 'android-lib', 'benchmarks:syncbench', 'projects:syncslides', 'projects:syncslides:app', 'projects:syncslidepresenter'