Merge "Java: remove a 1-day caveat from Android blessings"
diff --git a/android-lib/build.gradle b/android-lib/build.gradle
index ef28354..d4397b1 100644
--- a/android-lib/build.gradle
+++ b/android-lib/build.gradle
@@ -67,11 +67,11 @@
return System.properties['os.name'].toLowerCase()
}
-def v23Root = VanadiumEnvironment.getVanadiumEnvironment().v23Root.getAbsolutePath()
+def jiriRoot = VanadiumEnvironment.getVanadiumEnvironment().jiriRoot.getAbsolutePath()
class VanadiumEnvironment {
- File v23Root;
- File v23Bin;
+ File jiriRoot;
+ File jiriBin;
File thirdPartyGoBinDir;
public static getVanadiumEnvironment() {
@@ -83,32 +83,32 @@
+ "https://github.com/vanadium/docs/blob/master/installation.md")
}
- result.v23Root = new File(System.getenv()['JIRI_ROOT'])
- result.v23Bin = new File(result.v23Root, ['devtools', 'bin', 'v23'].join(File.separator))
- if (!result.v23Bin.exists() || !result.v23Bin.isFile() || !result.v23Bin.canExecute()) {
+ result.jiriRoot = new File(System.getenv()['JIRI_ROOT'])
+ result.jiriBin = new File(result.jiriRoot, ['devtools', 'bin', 'jiri'].join(File.separator))
+ if (!result.jiriBin.exists() || !result.jiriBin.isFile() || !result.jiriBin.canExecute()) {
throw new InvalidUserDataException(
- result.v23Bin.toString() + " does not exist or is not an executable file. "
+ result.jiriBin.toString() + " does not exist or is not an executable file. "
+ "Please follow the Vanadium installation instructions at "
+ "https://github.com/vanadium/docs/blob/master/installation.md")
}
- result.thirdPartyGoBinDir = new File(result.v23Root,
+ result.thirdPartyGoBinDir = new File(result.jiriRoot,
['third_party', 'android', 'go', 'bin'].join(File.separator))
def thirdPartyGoBin = new File(result.thirdPartyGoBinDir, 'go')
if (!thirdPartyGoBin.exists() || !thirdPartyGoBin.isFile() || !thirdPartyGoBin.canExecute()) {
throw new InvalidUserDataException(
thirdPartyGoBin.toString() + " does not exist or is not an executable file. "
+ "You probably didn't install the android profile. Try running\n\n"
- + "v23 profile install android\n\nand then try building again.")
+ + "jiri profile install android\n\nand then try building again.")
}
- def syncbaseOutputDir = new File(result.v23Root,
+ def syncbaseOutputDir = new File(result.jiriRoot,
['third_party', 'cout', 'android_arm', 'leveldb'].join(File.separator))
if (!syncbaseOutputDir.exists() || !syncbaseOutputDir.isDirectory()) {
throw new InvalidUserDataException(
syncbaseOutputDir.toString() + " does not exist or is not a directory. "
+ "You probably didn't install the android syncbase profile. Try running\n\n"
- + "GOOS=android GOARCH=arm v23 profile install syncbase"
+ + "GOOS=android GOARCH=arm jiri profile install syncbase"
+ "\n\nand then try building again"
)
}
@@ -139,13 +139,13 @@
environment 'JIRI_PROFILE': 'android'
environment 'PATH': [env.thirdPartyGoBinDir.getAbsolutePath(), existingPath].join(File.pathSeparator)
- commandLine env.v23Bin.getAbsolutePath(), 'go', 'install',
+ commandLine env.jiriBin.getAbsolutePath(), 'go', 'install',
'-buildmode=c-shared', '-v', '-tags', 'android', 'v.io/x/jni/main'
}
// Copy the shared library to its ultimate destination.
task copyVanadiumLib(type: Copy, dependsOn: goBuildVanadiumLib) {
- from v23Root + '/release/go/pkg/android_arm/v.io/x/jni'
+ from jiriRoot + '/release/go/pkg/android_arm/v.io/x/jni'
into 'src/main/jniLibs/armeabi-v7a'
include 'main.a'
rename 'main.a', 'libv23.so'
diff --git a/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java b/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
index 299da8f..83c9da9 100644
--- a/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
+++ b/android-lib/src/main/java/io/v/android/impl/google/rpc/protocols/bt/Bluetooth.java
@@ -15,7 +15,6 @@
import org.joda.time.Duration;
import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle
index 0ab89cc..419a774 100644
--- a/gradle-plugin/build.gradle
+++ b/gradle-plugin/build.gradle
@@ -36,7 +36,7 @@
task buildVdl(type: Exec) {
description 'Build the VDL tool'
group 'Build'
- commandLine 'v23', 'go', 'build', '-o', 'build/vdltool/vdl-' + getOsName(), 'v.io/x/ref/cmd/vdl'
+ commandLine 'jiri', 'go', 'build', '-o', 'build/vdltool/vdl-' + getOsName(), 'v.io/x/ref/cmd/vdl'
}
task natives(type: Jar, dependsOn: buildVdl) {
diff --git a/lib/build.gradle b/lib/build.gradle
index c11d684..640b2ff 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -40,16 +40,16 @@
return System.properties['os.name'].toLowerCase()
}
-def v23Root = VanadiumEnvironment.getVanadiumEnvironment().v23Root.getAbsolutePath()
-def v23Bin = VanadiumEnvironment.getVanadiumEnvironment().v23Bin.getAbsolutePath()
-def vdlPath = [v23Root, 'release', 'go', 'src'].join(File.separator)
-def vdlBin = [v23Root, 'release', 'go', 'bin', 'vdl'].join(File.separator)
+def jiriRoot = VanadiumEnvironment.getVanadiumEnvironment().jiriRoot.getAbsolutePath()
+def jiriBin = VanadiumEnvironment.getVanadiumEnvironment().jiriBin.getAbsolutePath()
+def vdlPath = [jiriRoot, 'release', 'go', 'src'].join(File.separator)
+def vdlBin = [jiriRoot, 'release', 'go', 'bin', 'vdl'].join(File.separator)
sourceSets.main.java.srcDirs += 'generated-src/vdl'
class VanadiumEnvironment {
- File v23Root;
- File v23Bin;
+ File jiriRoot;
+ File jiriBin;
File thirdPartyGoBinDir;
public static getVanadiumEnvironment() {
@@ -61,30 +61,30 @@
+ "https://github.com/vanadium/docs/blob/master/installation.md")
}
- result.v23Root = new File(System.getenv()['JIRI_ROOT'])
- result.v23Bin = new File(result.v23Root, ['devtools', 'bin', 'v23'].join(File.separator))
- if (!result.v23Bin.exists() || !result.v23Bin.isFile() || !result.v23Bin.canExecute()) {
+ result.jiriRoot = new File(System.getenv()['JIRI_ROOT'])
+ result.jiriBin = new File(result.jiriRoot, ['devtools', 'bin', 'jiri'].join(File.separator))
+ if (!result.jiriBin.exists() || !result.jiriBin.isFile() || !result.jiriBin.canExecute()) {
throw new InvalidUserDataException(
- result.v23Bin.toString() + " does not exist or is not an executable file. "
+ result.jiriBin.toString() + " does not exist or is not an executable file. "
+ "Please follow the Vanadium installation instructions at "
+ "https://github.com/vanadium/docs/blob/master/installation.md")
}
- result.thirdPartyGoBinDir = new File(result.v23Root,
+ result.thirdPartyGoBinDir = new File(result.jiriRoot,
['third_party', 'java', 'go', 'bin'].join(File.separator))
def thirdPartyGoBin = new File(result.thirdPartyGoBinDir, 'go')
if (!thirdPartyGoBin.exists() || !thirdPartyGoBin.isFile() || !thirdPartyGoBin.canExecute()) {
throw new InvalidUserDataException(
thirdPartyGoBin.toString() + " does not exist or is not an executable file. "
+ "You probably didn't install the java profile. Try running\n\n"
- + "v23 profile install java\n\nand then try building again.")
+ + "jiri profile install java\n\nand then try building again.")
}
return result
}
}
task buildVdlTool(type: Exec) {
- commandLine v23Bin, 'go', 'install', 'v.io/x/ref/cmd/vdl'
+ commandLine jiriBin, 'go', 'install', 'v.io/x/ref/cmd/vdl'
}
task generateVdl(type: Exec, dependsOn: buildVdlTool) {
@@ -101,7 +101,7 @@
throw new InvalidUserDataException("The JAVA_HOME environment variable is not set. "
+ "Please set it to the root of a JDK installation directory. If JDK isn't "
+ "installed, you probably didn't install the java profile: try running\n\n"
- + "v23 profile install java\n\nand then try building again.")
+ + "jiri profile install java\n\nand then try building again.")
}
if (!isAmd64()) {
throw new InvalidUserDataException("Java Vanadium builds only enabled on amd64 "
@@ -123,21 +123,21 @@
environment 'JIRI_PROFILE': 'java'
environment 'PATH': [env.thirdPartyGoBinDir.getAbsolutePath(), existingPath].join(File.pathSeparator)
- commandLine env.v23Bin.getAbsolutePath(), 'go', 'install',
+ commandLine env.jiriBin.getAbsolutePath(), 'go', 'install',
'-buildmode=c-shared', '-v', '-tags', 'java', 'v.io/x/jni/main'
}
// Copy the shared library to its ultimate destination.
if (isLinux()) {
task copyVanadiumLib(type: Copy, dependsOn: goBuildVanadiumLib) {
- from v23Root + '/release/go/pkg/linux_amd64_shared/v.io/x/jni'
+ from jiriRoot + '/release/go/pkg/linux_amd64_shared/v.io/x/jni'
into 'build/libs'
include 'main.a'
rename 'main.a', 'libv23.so'
}
} else { // darwin
task copyVanadiumLib(type: Copy, dependsOn: goBuildVanadiumLib) {
- from v23Root + '/release/go/pkg/darwin_amd64/v.io/x/jni'
+ from jiriRoot + '/release/go/pkg/darwin_amd64/v.io/x/jni'
into 'build/libs'
include 'main.a'
rename 'main.a', 'libv23.dylib'
diff --git a/lib/src/main/java/io/v/v23/security/Blessings.java b/lib/src/main/java/io/v/v23/security/Blessings.java
index 302b003..e0f6c91 100644
--- a/lib/src/main/java/io/v/v23/security/Blessings.java
+++ b/lib/src/main/java/io/v/v23/security/Blessings.java
@@ -6,6 +6,9 @@
import com.google.common.base.Joiner;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
@@ -27,35 +30,36 @@
* <p>
* See also: <a href="https://github.com/vanadium/docs/blob/master/glossary.md#blessing">https://github.com/vanadium/docs/blob/master/glossary.md#blessing</a>.
*/
-public class Blessings implements Serializable {
+public final class Blessings implements Serializable {
private static final long serialVersionUID = 1L;
- private static native Blessings nativeCreate(WireBlessings wire) throws VException;
+ private static native long nativeCreate(WireBlessings wire) throws VException;
private static native Blessings nativeCreateUnion(Blessings[] blessings) throws VException;
public static Blessings create(WireBlessings wire) {
try {
- return nativeCreate(wire);
+ return new Blessings(nativeCreate(wire), wire);
} catch (VException e) {
- throw new RuntimeException(e);
+ throw new RuntimeException("Couldn't create blessings from WireBlessings", e);
}
}
- public static Blessings create(List<List<VCertificate>> certChains) {
- return create(new WireBlessings(certChains));
- }
-
static Blessings createUnion(Blessings... blessings) throws VException {
return nativeCreateUnion(blessings);
}
- private final long nativePtr;
- private final WireBlessings wire; // non-null
+ private long nativePtr;
+ private volatile WireBlessings wire; // can be null
private native ECPublicKey nativePublicKey(long nativePtr) throws VException;
private native Blessings nativeSigningBlessings(long nativePtr) throws VException;
+ private native WireBlessings nativeWireFormat(long nativePtr) throws VException;
+
private native void nativeFinalize(long nativePtr);
- // TODO(sjayanti): a method to get all the non-signing blessings in a given blessings object.
+
+ private Blessings(long nativePtr) {
+ this.nativePtr = nativePtr;
+ }
private Blessings(long nativePtr, WireBlessings wire) {
this.nativePtr = nativePtr;
@@ -68,7 +72,7 @@
*/
public ECPublicKey publicKey() {
try {
- return nativePublicKey(this.nativePtr);
+ return nativePublicKey(nativePtr);
} catch (VException e) {
throw new RuntimeException("Couldn't get public key", e);
}
@@ -80,8 +84,8 @@
* The return value may be {@code null} if the blessings are empty.
*/
public Blessings signingBlessings() {
- try {
- return nativeSigningBlessings(this.nativePtr);
+ try {
+ return nativeSigningBlessings(nativePtr);
} catch (VException e) {
throw new RuntimeException("Couldn't get signing blessings", e);
}
@@ -91,14 +95,55 @@
* Returns the blessings in the wire format.
*/
public WireBlessings wireFormat() {
- return this.wire;
+ // Check the cache first as nativeWireFormat() is an expensive operation.
+ synchronized (this) {
+ if (wire != null) {
+ return wire;
+ }
+ }
+ WireBlessings ret = null;
+ try {
+ ret = nativeWireFormat(nativePtr);
+ } catch (VException e) {
+ throw new RuntimeException("Couldn't get wire blessings representation.", e);
+ }
+ synchronized (this) {
+ if (wire == null) {
+ wire = ret;
+ }
+ }
+ return ret;
}
/**
* Returns {@code true} iff the blessings are empty.
*/
public boolean isEmpty() {
- return this.wire.getCertificateChains().isEmpty();
+ return wireFormat().getCertificateChains().isEmpty();
+ }
+
+ /**
+ * Returns the certificate chains stored inside the blessings.
+ */
+ public List<List<VCertificate>> getCertificateChains() {
+ return wireFormat().getCertificateChains();
+ }
+
+ long nativePtr() {
+ return nativePtr;
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.writeObject(wireFormat());
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+ wire = (WireBlessings) in.readObject();
+ try {
+ nativePtr = nativeCreate(wire);
+ } catch (VException e) {
+ throw new IOException("Couldn't create native blessings.", e);
+ }
}
@Override
@@ -107,17 +152,20 @@
if (obj == null) return false;
if (!(obj instanceof Blessings)) return false;
Blessings other = (Blessings) obj;
- return this.wireFormat().equals(other.wireFormat());
+ return wireFormat().equals(other.wireFormat());
}
+
@Override
public int hashCode() {
- return this.wire.hashCode();
+ return wireFormat().hashCode();
}
+
@Override
public String toString() {
- List<String> chains = new ArrayList<>(getCertificateChains().size());
+ List<String> chains = new ArrayList<String>(getCertificateChains().size());
for (List<VCertificate> certificateChain : getCertificateChains()) {
- List<String> certificateNames = new ArrayList<>(certificateChain.size());
+ List<String> certificateNames =
+ new ArrayList<String>(certificateChain.size());
for (VCertificate certificate : certificateChain) {
certificateNames.add(certificate.getExtension());
}
@@ -127,10 +175,6 @@
}
@Override
protected void finalize() {
- nativeFinalize(this.nativePtr);
- }
-
- public List<List<VCertificate>> getCertificateChains() {
- return wire.getCertificateChains();
+ nativeFinalize(nativePtr);
}
}
diff --git a/lib/src/test/java/io/v/v23/security/BlessingsTest.java b/lib/src/test/java/io/v/v23/security/BlessingsTest.java
index cc6e884..ca14cc7 100644
--- a/lib/src/test/java/io/v/v23/security/BlessingsTest.java
+++ b/lib/src/test/java/io/v/v23/security/BlessingsTest.java
@@ -12,19 +12,21 @@
import static com.google.common.truth.Truth.assertThat;
import io.v.v23.V;
-import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.security.interfaces.ECPublicKey;
-import java.util.ArrayList;
import java.util.List;
/**
* Tests the default Blessings implementation.
*/
public class BlessingsTest extends TestCase {
- public void testPublicKey() throws VException {
+ public void testPublicKey() throws Exception {
V.init();
VPrincipal p1 = VSecurity.newPrincipal();
VPrincipal p2 = VSecurity.newPrincipal();
@@ -41,7 +43,7 @@
}
}
- public void testVomEncodeDecode() throws VException {
+ public void testVomEncodeDecode() throws Exception {
V.init();
VPrincipal p = VSecurity.newPrincipal();
Blessings alice = p.blessSelf("alice");
@@ -52,7 +54,28 @@
}
}
- public void testSigningBlessings() throws VException {
+ public void testSerialization() throws Exception {
+ V.init();
+ VPrincipal p = VSecurity.newPrincipal();
+ Blessings blessings = p.blessSelf("alice");
+
+ // Write
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(data);
+ out.writeObject(blessings);
+ out.close();
+
+ // Read
+ final ObjectInputStream in =
+ new ObjectInputStream(new ByteArrayInputStream(data.toByteArray()));
+
+ // Verify
+ final Object copy = in.readObject();
+ assertThat(copy).isEqualTo(blessings);
+ assertThat(copy.hashCode()).isEqualTo(copy.hashCode());
+ }
+
+ public void testSigningBlessings() throws Exception {
V.init();
VPrincipal p = VSecurity.newPrincipal();
ECPublicKey pk = p.publicKey();
@@ -71,5 +94,4 @@
assertThat(union.signingBlessings().getCertificateChains().size()).isEqualTo(1);
}
-
}
diff --git a/lib/src/test/java/io/v/v23/security/CaveatTest.java b/lib/src/test/java/io/v/v23/security/CaveatTest.java
index 1263717..3e78c3e 100644
--- a/lib/src/test/java/io/v/v23/security/CaveatTest.java
+++ b/lib/src/test/java/io/v/v23/security/CaveatTest.java
@@ -25,7 +25,7 @@
public void testMethodCaveat() throws VException {
VContext context = V.init();
VPrincipal p1 = VSecurity.newPrincipal();
- Blessings alice = p1.blessSelf("alice", VSecurity.newMethodCaveat("succeed"));
+ Blessings alice = p1.blessSelf("alice", VSecurity.newMethodCaveat("Succeed"));
p1.addToRoots(alice);
{
Call call = VSecurity.newCall(
@@ -35,7 +35,7 @@
}
{
Call call = VSecurity.newCall(
- new CallParams().withLocalPrincipal(p1).withMethod("fail"));
+ new CallParams().withLocalPrincipal(p1).withMethod("Fail"));
String[] result = VSecurity.getRemoteBlessingNames(context, call);
assertThat(result != null).isTrue();
assertThat(Arrays.asList(result)).containsExactly();
diff --git a/lib/src/test/java/io/v/v23/vdl/SerializableTest.java b/lib/src/test/java/io/v/v23/vdl/SerializableTest.java
index ec27f15..c6d27ac 100644
--- a/lib/src/test/java/io/v/v23/vdl/SerializableTest.java
+++ b/lib/src/test/java/io/v/v23/vdl/SerializableTest.java
@@ -19,21 +19,21 @@
public class SerializableTest extends junit.framework.TestCase {
public void testSerializable() throws IOException, ClassNotFoundException {
for (TestCase test : Constants.TESTS) {
- final Object value = test.getValue().getElem();
+ Object value = test.getValue().getElem();
if (!(value instanceof VdlValue)) continue;
// Write
- final ByteArrayOutputStream data = new ByteArrayOutputStream();
- final ObjectOutputStream out = new ObjectOutputStream(data);
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(data);
out.writeObject(value);
out.close();
// Read
- final ObjectInputStream in =
+ ObjectInputStream in =
new ObjectInputStream(new ByteArrayInputStream(data.toByteArray()));
// Verify
- final Object copy = in.readObject();
+ Object copy = in.readObject();
assertEquals(value, copy);
assertEquals(value.hashCode(), copy.hashCode());
}
diff --git a/projects/syncslides/app/build.gradle b/projects/syncslides/app/build.gradle
index df1637f..c00daee 100644
--- a/projects/syncslides/app/build.gradle
+++ b/projects/syncslides/app/build.gradle
@@ -6,7 +6,7 @@
defaultConfig {
applicationId "io.v.android.apps.syncslides"
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 23
versionCode 1
versionName "1.0"
@@ -23,4 +23,6 @@
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:design:23.0.1'
+ compile 'com.android.support:recyclerview-v7:23.0.1'
+ compile 'com.android.support:cardview-v7:23.0.1'
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserActivity.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserActivity.java
index 89cda47..73e857b 100644
--- a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserActivity.java
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserActivity.java
@@ -4,17 +4,11 @@
package io.v.android.apps.syncslides;
-import android.app.Activity;
-import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
-import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.View;
-import android.view.ViewGroup;
import android.support.v4.widget.DrawerLayout;
public class PresentationChooserActivity extends AppCompatActivity
@@ -74,62 +68,4 @@
return super.onCreateOptionsMenu(menu);
}
- /**
- * This fragment contains the list of presentations as well as the FAB to create a new
- * presentation.
- */
- public static class PresentationChooserFragment extends Fragment {
- /**
- * The fragment argument representing the section number for this
- * fragment.
- */
- private static final String ARG_SECTION_NUMBER = "section_number";
-
- /**
- * Returns a new instance of this fragment for the given section
- * number.
- */
- public static PresentationChooserFragment newInstance(int sectionNumber) {
- PresentationChooserFragment fragment = new PresentationChooserFragment();
- Bundle args = new Bundle();
- args.putInt(ARG_SECTION_NUMBER, sectionNumber);
- fragment.setArguments(args);
- return fragment;
- }
-
- public PresentationChooserFragment() {
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_presentation_chooser, container,
- false);
- FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(
- R.id.new_presentation_fab);
- fab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- newPresentation();
- }
- });
- return rootView;
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- ((PresentationChooserActivity) activity).onSectionAttached(
- getArguments().getInt(ARG_SECTION_NUMBER));
- }
-
- /**
- * Import a presentation so it shows up in the list of all presentations.
- */
- private void newPresentation() {
- // TODO(afergan): Hook up new presentation screen here.
- Log.i(TAG, "newPresentation");
- }
- }
-
}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserFragment.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserFragment.java
new file mode 100644
index 0000000..1a4ab43
--- /dev/null
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationChooserFragment.java
@@ -0,0 +1,86 @@
+// 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.app.Activity;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * This fragment contains the list of presentations as well as the FAB to create a new
+ * presentation.
+ */
+public class PresentationChooserFragment extends Fragment {
+ /**
+ * The fragment argument representing the section number for this
+ * fragment.
+ */
+ private static final String ARG_SECTION_NUMBER = "section_number";
+ private static final String TAG = "ChooserFragment";
+ private RecyclerView mRecyclerView;
+ private GridLayoutManager mLayoutManager;
+ private PresentationListAdapter mAdapter;
+
+ /**
+ * Returns a new instance of this fragment for the given section
+ * number.
+ */
+ public static PresentationChooserFragment newInstance(int sectionNumber) {
+ PresentationChooserFragment fragment = new PresentationChooserFragment();
+ Bundle args = new Bundle();
+ args.putInt(ARG_SECTION_NUMBER, sectionNumber);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_presentation_chooser, container,
+ false);
+ FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(
+ R.id.new_presentation_fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ newPresentation();
+ }
+ });
+
+ mRecyclerView = (RecyclerView) rootView.findViewById(R.id.presentation_grid);
+ mRecyclerView.setHasFixedSize(true);
+
+ // TODO(kash): Dynamically set the span based on the screen width.
+ mLayoutManager = new GridLayoutManager(getContext(), 2);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+
+ mAdapter = new PresentationListAdapter();
+ mRecyclerView.setAdapter(mAdapter);
+ return rootView;
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ ((PresentationChooserActivity) activity).onSectionAttached(
+ getArguments().getInt(ARG_SECTION_NUMBER));
+ }
+
+ /**
+ * Import a presentation so it shows up in the list of all presentations.
+ */
+ private void newPresentation() {
+ // TODO(afergan): Hook up new presentation screen here.
+ Log.i(TAG, "newPresentation");
+ }
+
+}
diff --git a/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationListAdapter.java b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationListAdapter.java
new file mode 100644
index 0000000..b14c3e5
--- /dev/null
+++ b/projects/syncslides/app/src/main/java/io/v/android/apps/syncslides/PresentationListAdapter.java
@@ -0,0 +1,61 @@
+// 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.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.Toolbar;
+
+/**
+ * Provides a list of presentations to be shown in the RecyclerView of the
+ * PresentationChooserFragment.
+ */
+public class PresentationListAdapter
+ extends RecyclerView.Adapter<PresentationListAdapter.ViewHolder> {
+ // TODO(kash): Replace this static data with syncbase.
+ private static final int[] THUMBS = {
+ R.drawable.thumb_presentation1,
+ R.drawable.thumb_presentation2,
+ R.drawable.thumb_presentation3
+ };
+ private static final String[] TITLES = {"Presentation 1", "Presentation 2", "Presentation 3"};
+
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
+ View v = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.presentation_card, parent, false);
+ // TODO(kash): Add a menu that allows the user to delete a presentation.
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int i) {
+ holder.mToolbar.setTitle(TITLES[i]);
+ // TODO(kash): We need to say when the user last viewed the presentation or show
+ // that the presentation is active. Either use the subtitle for this or create
+ // a custom view for both the title and subtitle.
+ holder.mThumb.setImageResource(THUMBS[i]);
+ }
+
+ @Override
+ public int getItemCount() {
+ return TITLES.length;
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public final ImageView mThumb;
+ public final Toolbar mToolbar;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ mThumb = (ImageView) itemView.findViewById(R.id.presentation_thumb);
+ mToolbar = (Toolbar) itemView.findViewById(R.id.presentation_card_toolbar);
+ }
+ }
+}
diff --git a/projects/syncslides/app/src/main/res/drawable/thumb_presentation1.png b/projects/syncslides/app/src/main/res/drawable/thumb_presentation1.png
new file mode 100644
index 0000000..851a751
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/drawable/thumb_presentation1.png
Binary files differ
diff --git a/projects/syncslides/app/src/main/res/drawable/thumb_presentation2.png b/projects/syncslides/app/src/main/res/drawable/thumb_presentation2.png
new file mode 100644
index 0000000..4fff1a8
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/drawable/thumb_presentation2.png
Binary files differ
diff --git a/projects/syncslides/app/src/main/res/drawable/thumb_presentation3.png b/projects/syncslides/app/src/main/res/drawable/thumb_presentation3.png
new file mode 100644
index 0000000..8708a4e
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/drawable/thumb_presentation3.png
Binary files differ
diff --git a/projects/syncslides/app/src/main/res/layout/fragment_presentation_chooser.xml b/projects/syncslides/app/src/main/res/layout/fragment_presentation_chooser.xml
index f109682..8526cca 100644
--- a/projects/syncslides/app/src/main/res/layout/fragment_presentation_chooser.xml
+++ b/projects/syncslides/app/src/main/res/layout/fragment_presentation_chooser.xml
@@ -1,9 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
+<!-- TODO(kash): Use colors from the theme once we have one. -->
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:background="#eee9e9">
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/presentation_grid"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="vertical"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/new_presentation_fab"
diff --git a/projects/syncslides/app/src/main/res/layout/presentation_card.xml b/projects/syncslides/app/src/main/res/layout/presentation_card.xml
new file mode 100644
index 0000000..0618da7
--- /dev/null
+++ b/projects/syncslides/app/src/main/res/layout/presentation_card.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.CardView
+ android:id="@+id/presentation_card"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/presentation_card_width"
+ android:layout_height="wrap_content"
+ android:layout_margin="@dimen/presentation_card_margin"
+ android:layout_gravity="center">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- A thumbnail of the presentation title slide. -->
+ <ImageView
+ android:id="@+id/presentation_thumb"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scaleType="centerCrop"/>
+
+ <!-- Display the title of the presentation and a menu to configure it. -->
+ <Toolbar
+ android:id="@+id/presentation_card_toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"></Toolbar>
+
+ </LinearLayout>
+
+</android.support.v7.widget.CardView>
diff --git a/projects/syncslides/app/src/main/res/values/dimens.xml b/projects/syncslides/app/src/main/res/values/dimens.xml
index 6d38062..222e45b 100644
--- a/projects/syncslides/app/src/main/res/values/dimens.xml
+++ b/projects/syncslides/app/src/main/res/values/dimens.xml
@@ -9,4 +9,7 @@
<dimen name="fab_margin">16dp</dimen>
<dimen name="fab_elevation">4dp</dimen>
+
+ <dimen name="presentation_card_width">200dp</dimen>
+ <dimen name="presentation_card_margin">5dp</dimen>
</resources>