reader/android: add device meta-data

- add DeviceType to the Device schema (e.g., "Android", "Web")
- add DeviceInfoFactory class, which creates a Device object with the
  correct information for the current Android device.

Change-Id: I639e07d3c4c4a7ea1fe7b946d11baf7c885131e1
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
index 87cdeab..38a2e37 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/DB.java
@@ -9,6 +9,7 @@
 import android.content.Intent;
 
 import io.v.android.apps.reader.model.Listener;
+import io.v.android.apps.reader.vdl.Device;
 import io.v.android.apps.reader.vdl.DeviceSet;
 import io.v.android.apps.reader.vdl.File;
 
@@ -90,6 +91,12 @@
     DBList<File> getFileList();
 
     /**
+     * Gets the list of devices of this user.
+     * @return a list of devices.
+     */
+    DBList<Device> getDeviceList();
+
+    /**
      * Gets the list of currently active device sets.
      * @return a list of device sets.
      */
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
index 8d4e4e8..4ca5244 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/FakeDB.java
@@ -7,11 +7,14 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import io.v.android.apps.reader.model.DeviceInfoFactory;
 import io.v.android.apps.reader.model.Listener;
+import io.v.android.apps.reader.vdl.Device;
 import io.v.android.apps.reader.vdl.DeviceSet;
 import io.v.android.apps.reader.vdl.File;
 
@@ -31,17 +34,37 @@
     };
 
     private FakeFileList mFileList;
+    private FakeDeviceList mDeviceList;
     private FakeDeviceSetList mDeviceSetList;
 
     public FakeDB(Context context) {
         mFileList = new FakeFileList();
+        mDeviceList = new FakeDeviceList(context);
         mDeviceSetList = new FakeDeviceSetList();
     }
 
-    static class FakeFileList implements DBList<File> {
+    static abstract class BaseFakeList<E> implements DBList<E> {
+
+        private Listener mListener;
+
+        @Override
+        public void setListener(Listener listener) {
+            // This fake list never calls the notify methods.
+            // Just check if the listener is set only once.
+            assert mListener == null;
+            mListener = listener;
+        }
+
+        @Override
+        public void discard() {
+            // Nothing to do.
+        }
+
+    }
+
+    static class FakeFileList extends BaseFakeList<File> {
 
         private List<File> mFiles;
-        private Listener mListener;
 
         public FakeFileList() {
             mFiles = new ArrayList<>();
@@ -69,25 +92,33 @@
         public File getItem(int position) {
             return mFiles.get(position);
         }
+    }
 
-        @Override
-        public void setListener(Listener listener) {
-            // This fake list never calls the notify methods.
-            // Just check if the listener is set only once.
-            assert mListener == null;
-            mListener = listener;
+    static class FakeDeviceList extends BaseFakeList<Device> {
+
+        private static final String TAG = FakeDeviceList.class.getSimpleName();
+
+        private Context mContext;
+
+        public FakeDeviceList(Context context) {
+            mContext = context;
+            Log.i(TAG, "Device Info: " + getItem(0));
         }
 
         @Override
-        public void discard() {
-            // Nothing to do.
+        public int getItemCount() {
+            return 1;
+        }
+
+        @Override
+        public Device getItem(int position) {
+            return DeviceInfoFactory.get(mContext);
         }
     }
 
-    static class FakeDeviceSetList implements DBList<DeviceSet> {
+    static class FakeDeviceSetList extends BaseFakeList<DeviceSet> {
 
         private List<DeviceSet> mDeviceSets;
-        private Listener mListener;
 
         public FakeDeviceSetList() {
             mDeviceSets = new ArrayList<>();
@@ -113,19 +144,6 @@
         public DeviceSet getItem(int position) {
             return mDeviceSets.get(position);
         }
-
-        @Override
-        public void setListener(Listener listener) {
-            // This fake list never calls the notify methods.
-            // Just check if the listener is set only once.
-            assert mListener == null;
-            mListener = listener;
-        }
-
-        @Override
-        public void discard() {
-            // Nothing to do.
-        }
     }
 
     public void init(Activity activity) {
@@ -144,6 +162,11 @@
     }
 
     @Override
+    public DBList<Device> getDeviceList() {
+        return mDeviceList;
+    }
+
+    @Override
     public DBList<DeviceSet> getDeviceSetList() {
         return mDeviceSetList;
     }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
index c72045c..1f9b636 100644
--- a/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
+++ b/android/app/src/main/java/io/v/android/apps/reader/db/SyncbaseDB.java
@@ -14,6 +14,7 @@
 import com.google.common.collect.ImmutableMap;
 
 import io.v.android.apps.reader.model.Listener;
+import io.v.android.apps.reader.vdl.Device;
 import io.v.android.apps.reader.vdl.DeviceSet;
 import io.v.android.apps.reader.vdl.File;
 import io.v.android.libs.security.BlessingsManager;
@@ -52,12 +53,18 @@
     private static final int BLESSING_REQUEST = 200;
     private static final String SYNCBASE_APP = "reader";
     private static final String SYNCBASE_DB = "db";
+
     private static final String FILES_TABLE = "files";
+    private static final String DEVICES_TABLE = "devices";
+    private static final String DEVICE_SETS_TABLE = "deviceSets";
 
     private Permissions mPermissions;
     private Context mContext;
     private VContext vContext;
+
     private Table mFiles;
+    private Table mDevices;
+    private Table mDeviceSets;
 
     SyncbaseDB(Context context) {
         mContext = context;
@@ -182,6 +189,16 @@
             if (!mFiles.exists(vContext)) {
                 mFiles.create(vContext, mPermissions);
             }
+
+            mDevices = db.getTable(DEVICES_TABLE);
+            if (!mDevices.exists(vContext)) {
+                mDevices.create(vContext, mPermissions);
+            }
+
+            mDeviceSets = db.getTable(DEVICE_SETS_TABLE);
+            if (!mDeviceSets.exists(vContext)) {
+                mDeviceSets.create(vContext, mPermissions);
+            }
         } catch (VException e) {
             handleError("Couldn't setup syncbase service: " + e.getMessage());
         }
@@ -214,6 +231,11 @@
     }
 
     @Override
+    public DBList<Device> getDeviceList() {
+        return new EmptyList<Device>();
+    }
+
+    @Override
     public DBList<DeviceSet> getDeviceSetList() {
         return new EmptyList<DeviceSet>();
     }
diff --git a/android/app/src/main/java/io/v/android/apps/reader/model/DeviceInfoFactory.java b/android/app/src/main/java/io/v/android/apps/reader/model/DeviceInfoFactory.java
new file mode 100644
index 0000000..1f067f7
--- /dev/null
+++ b/android/app/src/main/java/io/v/android/apps/reader/model/DeviceInfoFactory.java
@@ -0,0 +1,74 @@
+// 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.reader.model;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.os.Build;
+import android.provider.Settings;
+import android.view.Display;
+import android.view.WindowManager;
+
+import io.v.android.apps.reader.vdl.Device;
+import io.v.android.apps.reader.vdl.Screen;
+
+/**
+ * A factory class that provides the information of the current device.
+ */
+public class DeviceInfoFactory {
+
+    private static final String DEVICE_TYPE = "Android";
+
+    private static volatile Device instance;
+
+    /**
+     * Singleton method for getting the Device object that represents this device.
+     *
+     * @param context Android context
+     * @return Device object representing this device.
+     */
+    public static Device get(Context context) {
+        Device result = instance;
+        if (instance == null) {
+            synchronized (DeviceInfoFactory.class) {
+                result = instance;
+                if (result == null) {
+                    String id = Settings.Secure.getString(
+                            context.getContentResolver(),
+                            Settings.Secure.ANDROID_ID);
+
+                    String name = Build.MODEL;
+
+                    String arch = System.getProperty("os.arch");
+
+                    Point size = getScreenSize(context);
+                    Screen screen = new Screen(size.x, size.y);
+
+                    instance = result = new Device(id, DEVICE_TYPE, name, arch, screen);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the screen size.
+     */
+    private static Point getScreenSize(Context context) {
+        WindowManager wm = (WindowManager) context
+                .getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+
+        // Make width <= height, regardless of the current screen orientation.
+        if (size.x > size.y) {
+            size.set(size.y, size.x);
+        }
+
+        return size;
+    }
+}
diff --git a/vdl/device.vdl b/vdl/device.vdl
index d742693..dd9cfe0 100644
--- a/vdl/device.vdl
+++ b/vdl/device.vdl
@@ -11,6 +11,7 @@
 
 type Device struct {
   Id string
+  DeviceType string
   Name string
   Arch string
   Screen Screen