Added the /permissions directory. permissions contains an example android application that provides a backend for managing permissions in between  devices in cross-device applications. The backend uses Firebase to communicate between different devices. example/EmailActivity uses this backend to cast email messages to other devices.

Change-Id: I19a390758629973851219c2fd8039f021ddc3462
diff --git a/permissions/.gitignore b/permissions/.gitignore
new file mode 100644
index 0000000..3c605de
--- /dev/null
+++ b/permissions/.gitignore
@@ -0,0 +1,19 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+
+# google-services / firebase configuration
+google-services.json
+
+# OSX files
+.DS_Store
+# Windows thumbnail db
+Thumbs.db
+
+#NDK
+obj/
\ No newline at end of file
diff --git a/permissions/app/proguard-rules.pro b/permissions/app/proguard-rules.pro
new file mode 100644
index 0000000..1dd87d6
--- /dev/null
+++ b/permissions/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/home/phamilton/Android/Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/Blessing.java b/permissions/app/src/main/java/examples/baku/io/permissions/Blessing.java
new file mode 100644
index 0000000..3a34af1
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/Blessing.java
@@ -0,0 +1,233 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Stack;
+import java.util.UUID;
+
+/**
+ * Created by phamilton on 7/9/16.
+ */
+public class Blessing implements Iterable<Blessing.Rule> {
+
+    private static final String KEY_PERMISSIONS = "_permissions";
+    private static final String KEY_RULES = "rules";
+
+    private String id;
+    //    private String pattern;
+    private String source;
+    private String target;
+    private DatabaseReference ref;
+    private DatabaseReference rulesRef;
+    private DataSnapshot snapshot;
+
+    final private Map<String, PermissionReference> refCache = new HashMap<>();
+
+    public Blessing(DataSnapshot snapshot) {
+        setSnapshot(snapshot);
+        this.id = snapshot.child("id").getValue(String.class);
+        this.target = snapshot.child("target").getValue(String.class);
+        if (snapshot.hasChild("source"))
+            this.source = snapshot.child("source").getValue(String.class);
+    }
+
+    public Blessing(String target, String source, DatabaseReference ref) {
+        setRef(ref);
+        setId(ref.getKey());
+        setSource(source);
+        setTarget(target);
+        ref.addListenerForSingleValueEvent(new ValueEventListener() {
+            @Override
+            public void onDataChange(DataSnapshot dataSnapshot) {
+                setSnapshot(dataSnapshot);
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+                databaseError.toException().printStackTrace();
+            }
+        });
+    }
+
+    public boolean isSynched() {
+        return snapshot != null;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+        ref.child("id").setValue(id);
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+        ref.child("source").setValue(source);
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+        ref.child("target").setValue(target);
+    }
+
+    public void setSnapshot(DataSnapshot snapshot) {
+        if (!snapshot.exists()) {
+            throw new IllegalArgumentException("empty snapshot");
+        }
+        this.snapshot = snapshot;
+        setRef(snapshot.getRef());
+    }
+
+    public Blessing setPermissions(String path, int permissions) {
+        getRef(path).setPermission(permissions);
+        return this;
+    }
+
+    public Blessing clearPermissions(String path) {
+        getRef(path).clearPermission();
+        return this;
+    }
+
+    //delete all permission above path
+    public Blessing revoke(String path) {
+        if (path != null) {
+            rulesRef.child(path).removeValue();
+        } else {
+            rulesRef.removeValue();
+        }
+        return this;
+    }
+
+    public PermissionReference getRef(String path) {
+        PermissionReference result = refCache.get(path);
+        if (result == null) {
+            result = new PermissionReference(rulesRef, path);
+            refCache.put(path, result);
+        }
+        return result;
+    }
+
+    public void setRef(DatabaseReference ref) {
+        this.ref = ref;
+        this.rulesRef = ref.child(KEY_RULES);
+    }
+
+    public int getPermissionAt(String path, int starting) {
+        if (!isSynched()) {   //snapshot not retrieved
+            return starting;
+        }
+        if (path == null) {
+            throw new IllegalArgumentException("illegal path value");
+        }
+        String[] pathItems = path.split("/");
+        DataSnapshot currentNode = snapshot;
+        if (currentNode.hasChild(KEY_PERMISSIONS)) {
+            starting |= currentNode.child(KEY_PERMISSIONS).getValue(Integer.class);
+        }
+        for (int i = 0; i < pathItems.length; i++) {
+            if (currentNode.hasChild(pathItems[i])) {
+                currentNode = snapshot.child(pathItems[i]);
+            } else {  //child doesn't exist
+                break;
+            }
+            if (currentNode.hasChild(KEY_PERMISSIONS)) {
+                starting |= currentNode.child(KEY_PERMISSIONS).getValue(Integer.class);
+            }
+        }
+        return starting;
+    }
+
+    @Override
+    public Iterator<Rule> iterator() {
+        if (!isSynched()) {
+            return null;
+        }
+        final Stack<DataSnapshot> nodeStack = new Stack<>();
+        nodeStack.push(snapshot.child(KEY_RULES));
+
+        final Stack<Rule> inheritanceStack = new Stack<>();
+        inheritanceStack.push(new Rule(null, 0)); //default rule
+
+        return new Iterator<Rule>() {
+            @Override
+            public boolean hasNext() {
+                return !nodeStack.isEmpty();
+            }
+
+            @Override
+            public Rule next() {
+                DataSnapshot node = nodeStack.pop();
+                Rule inheritedRule = inheritanceStack.pop();
+
+                Rule result = new Rule();
+                String key = node.getKey();
+                if (!KEY_RULES.equals(key)) {   //key_rules is the root directory
+                    if (inheritedRule.path != null) {
+                        result.path = inheritedRule.path + "/" + key;
+                    } else {
+                        result.path = key;
+                    }
+                }
+
+                result.permissions = inheritedRule.permissions;
+                if (node.hasChild(KEY_PERMISSIONS)) {
+                    result.permissions |= node.child(KEY_PERMISSIONS).getValue(Integer.class);
+                }
+                for (final DataSnapshot child : node.getChildren()) {
+                    if (child.getKey().startsWith("_")) { //ignore keys with '_' prefix
+                        continue;
+                    }
+                    nodeStack.push(child);
+                    inheritanceStack.push(result);
+                }
+                return result;
+            }
+
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    public static class Rule {
+        private String path;
+        private int permissions;
+
+        public Rule() {
+        }
+
+        public Rule(String path, int permissions) {
+            this.path = path;
+            this.permissions = permissions;
+        }
+
+        public String getPath() {
+            return path;
+        }
+
+        public int getPermissions() {
+            return permissions;
+        }
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/PermissionManager.java b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionManager.java
new file mode 100644
index 0000000..851ae81
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionManager.java
@@ -0,0 +1,374 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+
+/**
+ * Created by phamilton on 6/28/16.
+ */
+public class PermissionManager {
+
+    DatabaseReference mDatabaseRef;
+    DatabaseReference mBlessingsRef;
+    DatabaseReference mRequestsRef;
+
+    public static final int FLAG_DEFAULT = 0;
+    public static final int FLAG_WRITE = 1 << 0;
+    public static final int FLAG_READ = 1 << 1;
+    public static final int FLAG_PUSH = 1 << 2;     //2-way
+//    public static final int FLAG_REFER = 1 << 3;       //1-way
+
+    static final String KEY_PERMISSIONS = "_permissions";
+    static final String KEY_REQUESTS = "_requests";
+    static final String KEY_BLESSINGS = "_blessings";
+
+    private String mId;
+
+    final Map<String, PermissionRequest> mRequests = new HashMap<>();
+
+    //    final Map<String, Set<OnRequestListener>> requestListeners = new HashMap<>();
+    final Set<OnRequestListener> requestListeners = new HashSet<>();
+    final Multimap<String, OnReferralListener> referralListeners = HashMultimap.create();
+
+    final Map<String, Blessing> mBlessings = new HashMap<>();
+    //<targetId, blessingId>
+    //TODO: allow for multiple granted blessings per target
+    final Map<String, Blessing> mGrantedBlessings = new HashMap<>();
+
+    final Map<String, Integer> mCachedPermissions = new HashMap<>();
+    final Multimap<String, OnPermissionChangeListener> mPermissionValueEventListeners = HashMultimap.create();
+    final Multimap<String, String> mNearestAncestors = HashMultimap.create();
+
+
+    //TODO: replace string ownerId with Auth
+    public PermissionManager(final DatabaseReference databaseReference, String owner) {
+        this.mDatabaseRef = databaseReference;
+        this.mId = owner;
+
+        mRequestsRef = databaseReference.child(KEY_REQUESTS);
+        //TODO: only consider requests from sources within the constelattion
+        mRequestsRef.addChildEventListener(requestListener);
+
+        mBlessingsRef = mDatabaseRef.child(KEY_BLESSINGS);
+        mBlessingsRef.orderByChild("target").equalTo(mId).addChildEventListener(blessingListener);
+        mBlessingsRef.orderByChild("source").equalTo(mId).addListenerForSingleValueEvent(grantedBlessingListener);
+    }
+
+    void onBlessingUpdated(DataSnapshot snapshot) {
+        if (!snapshot.exists()) {
+            throw new IllegalArgumentException("snapshot value doesn't exist");
+        }
+        String key = snapshot.getKey();
+        Blessing blessing = mBlessings.get(key);
+        if (blessing == null) {
+            blessing = new Blessing(snapshot);
+            mBlessings.put(key, blessing);
+        } else {
+            blessing.setSnapshot(snapshot);
+        }
+
+        refreshPermissions();
+    }
+
+    //TODO: optimize this mess. Currently, recalculating entire permission tree.
+    void refreshPermissions() {
+        Map<String, Integer> updatedPermissions = new HashMap<>();
+        for (Blessing blessing : mBlessings.values()) {
+            if (blessing.isSynched()) {
+                for (Blessing.Rule rule : blessing) {
+                    String path = rule.getPath();
+                    if (updatedPermissions.containsKey(path)) {
+                        updatedPermissions.put(path, updatedPermissions.get(path) | rule.getPermissions());
+                    } else {
+                        updatedPermissions.put(path, rule.getPermissions());
+                    }
+                }
+            }
+        }
+
+        mNearestAncestors.clear();
+        for (String path : mPermissionValueEventListeners.keySet()) {
+            String nearestAncestor = getNearestCommonAncestor(path, updatedPermissions.keySet());
+            if (nearestAncestor != null) {
+                mNearestAncestors.put(nearestAncestor, path);
+            }
+        }
+
+        Set<String> changedPermissions = new HashSet<>();
+
+        Set<String> removedPermissions = new HashSet<>(mCachedPermissions.keySet());
+        removedPermissions.removeAll(updatedPermissions.keySet());
+        for (String path : removedPermissions) {
+            mCachedPermissions.remove(path);
+            String newPath = getNearestCommonAncestor(path, updatedPermissions.keySet());
+            changedPermissions.add(newPath);   //reset to default
+        }
+
+        for (String path : updatedPermissions.keySet()) {
+            int current = updatedPermissions.get(path);
+            if (!mCachedPermissions.containsKey(path)) {
+                mCachedPermissions.put(path, current);
+                changedPermissions.add(path);
+            } else {
+                int previous = mCachedPermissions.get(path);
+                if (previous != current) {
+                    mCachedPermissions.put(path, current);
+                    changedPermissions.add(path);
+                }
+            }
+        }
+
+        for (String path : changedPermissions) {
+            onPermissionsChange(path);
+        }
+
+
+    }
+
+    //call all the listeners effected by a permission change at this path
+    void onPermissionsChange(String path) {
+        int permission = getPermission(path);
+        if (mNearestAncestors.containsKey(path)) {
+            for (String listenerPath : mNearestAncestors.get(path)) {
+                if (mPermissionValueEventListeners.containsKey(listenerPath)) {
+                    for (OnPermissionChangeListener listener : mPermissionValueEventListeners.get(listenerPath)) {
+                        listener.onPermissionChange(permission);
+                    }
+                }
+            }
+        }
+    }
+
+    private ValueEventListener grantedBlessingListener = new ValueEventListener() {
+        @Override
+        public void onDataChange(DataSnapshot dataSnapshot) {
+            if (dataSnapshot.exists()) {
+                for (DataSnapshot blessingSnap : dataSnapshot.getChildren()) {
+                    Blessing blessing = new Blessing(blessingSnap);
+                    mGrantedBlessings.put(blessing.getId(), blessing);
+                }
+            }
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    void onBlessingRemoved(DataSnapshot snapshot) {
+        Blessing removedBlessing = mBlessings.remove(snapshot.getKey());
+        refreshPermissions();
+    }
+
+    public Blessing getGrantedBlessing(String target) {
+        return mGrantedBlessings.get(target);
+    }
+
+    static String getNearestCommonAncestor(String path, Set<String> ancestors) {
+        if (path.startsWith("/")) {
+            throw new IllegalArgumentException("Path can't start with /");
+        }
+        if (ancestors.contains(path)) {
+            return path;
+        }
+        String subpath = path;
+        int index;
+        while ((index = subpath.lastIndexOf("/")) != -1) {
+            subpath = subpath.substring(0, index);
+            if (ancestors.contains(subpath)) {
+                return subpath;
+            }
+        }
+
+        return null;
+    }
+
+    //return a blessing interface for granting/revoking permissions
+    public Blessing bless(String target) {
+        Blessing result = getGrantedBlessing(target);
+        if (result == null) {
+            result = new Blessing(target, this.mId, mBlessingsRef.push());
+            mGrantedBlessings.put(target, result);
+        }
+        return result;
+    }
+
+    private ChildEventListener requestListener = new ChildEventListener() {
+        @Override
+        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+            onBlessingUpdated(dataSnapshot);
+        }
+
+        @Override
+        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+            onBlessingUpdated(dataSnapshot);
+        }
+
+        @Override
+        public void onChildRemoved(DataSnapshot dataSnapshot) {
+            onBlessingRemoved(dataSnapshot);
+        }
+
+        @Override
+        public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    private void onRequestUpdated(DataSnapshot snapshot) {
+        if (!snapshot.exists()) return;
+
+        PermissionRequest request = snapshot.getValue(PermissionRequest.class);
+        if (request != null) {
+            mRequests.put(request.getId(), request);
+            //TODO: filter relevant requests
+            for (OnRequestListener listener : requestListeners) {
+                listener.onRequest(request);
+            }
+        }
+    }
+
+    //TODO: only notify listeners that returned true when the request was added
+    private void onRequestRemoved(DataSnapshot snapshot) {
+        mRequests.remove(snapshot.getKey());
+        PermissionRequest request = snapshot.getValue(PermissionRequest.class);
+        if (request != null) {
+            for (OnRequestListener listener : requestListeners) {
+                listener.onRequestRemoved(request);
+            }
+        }
+    }
+
+
+    private ChildEventListener blessingListener = new ChildEventListener() {
+        @Override
+        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+            onBlessingUpdated(dataSnapshot);
+        }
+
+        @Override
+        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+            onBlessingUpdated(dataSnapshot);
+        }
+
+        @Override
+        public void onChildRemoved(DataSnapshot dataSnapshot) {
+            onBlessingRemoved(dataSnapshot);
+        }
+
+        @Override
+        public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    public int getPermission(String path) {
+        if (mCachedPermissions.containsKey(path))
+            return mCachedPermissions.get(path);
+        int result = getCombinedPermission(path);
+        mCachedPermissions.put(path, result);
+        return result;
+    }
+
+    private int getCombinedPermission(String path) {
+        int current = 0;
+        for (Blessing blessing : mBlessings.values()) {
+            current = blessing.getPermissionAt(path, current);
+        }
+        return current;
+    }
+
+    public OnPermissionChangeListener addPermissionEventListener(String path, OnPermissionChangeListener listener) {
+        int current = FLAG_DEFAULT;
+        mPermissionValueEventListeners.put(path, listener);
+
+        String nearestAncestor = getNearestCommonAncestor(path, mCachedPermissions.keySet());
+        if (nearestAncestor != null) {
+            current = getPermission(nearestAncestor);
+            mNearestAncestors.put(nearestAncestor, path);
+        }
+        listener.onPermissionChange(current);
+        return listener;
+    }
+
+    public void removePermissionEventListener(String path, OnPermissionChangeListener listener) {
+        mPermissionValueEventListeners.remove(path, listener);
+
+        String nca = getNearestCommonAncestor(path, mCachedPermissions.keySet());
+        mNearestAncestors.remove(nca, path);
+
+    }
+
+    public void removeOnRequestListener(PermissionManager.OnRequestListener requestListener) {
+        requestListeners.remove(requestListener);
+    }
+
+    public PermissionManager.OnRequestListener addOnRequestListener(PermissionManager.OnRequestListener requestListener) {
+        requestListeners.add(requestListener);
+        return requestListener;
+    }
+
+    public void removeOnReferralListener(String path, OnReferralListener referralListener) {
+        referralListeners.remove(path, referralListener);
+    }
+
+    public OnReferralListener addOnReferralListener(String path, OnReferralListener referralListener) {
+        referralListeners.put(path, referralListener);
+        return referralListener;
+    }
+
+    public void refer(PermissionReferral referral) {
+    }
+
+    public void request(PermissionRequest request) {
+        if (request == null)
+            throw new IllegalArgumentException("null request");
+
+        DatabaseReference requestRef = mRequestsRef.push();
+        request.setId(requestRef.getKey());
+        requestRef.setValue(request);
+    }
+
+    public interface OnRequestListener {
+        boolean onRequest(PermissionRequest request);
+
+        void onRequestRemoved(PermissionRequest request);
+    }
+
+    public interface OnReferralListener {
+        void onReferral();
+    }
+
+    public interface OnPermissionChangeListener {
+        void onPermissionChange(int current);
+
+        void onCancelled(DatabaseError databaseError);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReference.java b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReference.java
new file mode 100644
index 0000000..566754f
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReference.java
@@ -0,0 +1,38 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+import android.provider.ContactsContract;
+
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class PermissionReference {
+
+    private DatabaseReference mPermissionReference;
+
+    private final Map<String, Integer> permissions = new HashMap<>();    //key is group path
+
+    public PermissionReference(DatabaseReference root, String path) {
+        this.mPermissionReference = root.child(path).child(PermissionManager.KEY_PERMISSIONS);
+    }
+
+    public void setPermission(int permission) {
+        mPermissionReference.setValue(permission);
+    }
+
+    public void clearPermission() {
+        mPermissionReference.removeValue();
+    }
+
+}
\ No newline at end of file
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReferral.java b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReferral.java
new file mode 100644
index 0000000..ac10f33
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionReferral.java
@@ -0,0 +1,11 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+/**
+ * Created by phamilton on 7/6/16.
+ */
+public class PermissionReferral {
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/PermissionRequest.java b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionRequest.java
new file mode 100644
index 0000000..e856113
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionRequest.java
@@ -0,0 +1,65 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+import java.security.Permission;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by phamilton on 6/28/16.
+ */
+
+//TODO: multiple resources (Request groups)
+public class PermissionRequest {
+
+    private String id;
+    private String source;
+    private Map<String, Integer> permissions = new HashMap<>();
+    private Map<String, String> description= new HashMap<>();
+
+    public PermissionRequest(){}
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public Map<String, Integer> getPermissions() {
+        return permissions;
+    }
+
+    public void setPermissions(Map<String, Integer> permissions) {
+        this.permissions = permissions;
+    }
+
+    public Map<String, String> getDescription() {
+        return description;
+    }
+
+    public void setDescription(Map<String, String> description) {
+        this.description = description;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public static class Builder{
+        private PermissionRequest request;
+
+        public Builder(String path){
+            this.request = new PermissionRequest();
+        }
+    }
+
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/PermissionService.java b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionService.java
new file mode 100644
index 0000000..756693c
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/PermissionService.java
@@ -0,0 +1,488 @@
+// Copyright 2016 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 examples.baku.io.permissions;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseException;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.ValueEventListener;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import examples.baku.io.permissions.discovery.DeviceData;
+import examples.baku.io.permissions.discovery.DevicePickerActivity;
+import examples.baku.io.permissions.examples.ComposeActivity;
+import examples.baku.io.permissions.examples.EmailActivity;
+import examples.baku.io.permissions.messenger.Messenger;
+import examples.baku.io.permissions.messenger.Message;
+
+public class PermissionService extends Service {
+
+    private static final String TAG = PermissionService.class.getSimpleName();
+
+    static void l(String msg) {
+        Log.e(TAG, msg);
+    }
+
+    private static boolean mRunning;
+
+    public static boolean isRunning() {
+        return mRunning;
+    }
+
+    static final int FOREGROUND_NOTIFICATION_ID = 3278;
+    static final int FOCUS_NOTIFICATION = 43254;
+
+    static final String KEY_BLESSINGS = PermissionManager.KEY_BLESSINGS;
+
+    NotificationManager mNotificationManager;
+
+    FirebaseDatabase mFirebaseDB;
+    DatabaseReference mDevicesReference;
+    DatabaseReference mRequestsReference;
+
+
+    DatabaseReference mMessengerReference;
+    Messenger mMessenger;
+
+    DatabaseReference mPermissionsReference;
+    PermissionManager mPermissionManager;
+    Blessing mDeviceBlessing;
+
+    DatabaseReference mLocalDeviceReference;
+
+    private String mDeviceId;
+
+    private IBinder mBinder = new PermissionServiceBinder();
+
+
+    private String mFocus;
+    private Map<String, DeviceData> mDiscovered = new HashMap<>();
+    private Map<String, Integer> mDiscoveredNotifications = new HashMap<>();
+
+    private HashSet<DiscoveryListener> mDiscoveryListener = new HashSet<>();
+
+    public void revokeAll() {
+        for (Blessing blessing : mPermissionManager.mGrantedBlessings.values()) {
+            blessing.revoke(null);
+        }
+    }
+
+    public interface DiscoveryListener {
+        void onChange(Map<String, DeviceData> devices);
+
+        void onDisassociate(String deviceId);
+    }
+
+    public class PermissionServiceBinder extends Binder {
+        public PermissionService getInstance() {
+            return PermissionService.this;
+        }
+    }
+
+    public PermissionService() {
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+
+        mDeviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
+                Settings.Secure.ANDROID_ID);
+
+
+        mFirebaseDB = FirebaseDatabase.getInstance();
+        mDevicesReference = mFirebaseDB.getReference("_devices");
+        mRequestsReference = mFirebaseDB.getReference("requests");
+
+        mPermissionsReference = mFirebaseDB.getReference("permissions");
+        mPermissionManager = new PermissionManager(mFirebaseDB.getReference(), mDeviceId);
+
+        mPermissionManager.addOnRequestListener(new PermissionManager.OnRequestListener() {
+            @Override
+            public boolean onRequest(PermissionRequest request) {
+                return true;
+            }
+
+            @Override
+            public void onRequestRemoved(PermissionRequest request) {
+
+            }
+        });
+
+        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+        initForegroundNotification();
+
+        registerDevice();
+        initDeviceBlessing();
+        initMessenger();
+        initDiscovery();
+
+        mRunning = true;
+    }
+
+    public Messenger getMessenger() {
+        return mMessenger;
+    }
+
+    public String getFocus() {
+        return mFocus;
+    }
+
+
+    public void setFocus(String dId) {
+        mFocus = dId;
+        if (!mDiscovered.containsKey(dId)) return;
+
+        DeviceData device = mDiscovered.get(dId);
+        String title = device.getName();
+        String subtitle = device.getId();   //default
+        if (device.getStatus() != null && device.getStatus().containsKey("description")) {
+            subtitle = device.getStatus().get("description");
+        }
+        int icon = R.drawable.ic_phone_android_black_24dp;
+
+        Intent dismissIntent = new Intent(this, PermissionService.class);
+        dismissIntent.putExtra("type", "dismiss");
+        dismissIntent.putExtra("deviceId", dId);
+        PendingIntent dismissPending = PendingIntent.getService(this, 0, dismissIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        Notification.Builder notificationBuilder = new Notification.Builder(this)
+                .setContentTitle(title)
+                .setContentText(subtitle)
+                .setSmallIcon(icon)
+                .setVibrate(new long[]{100})
+                .setPriority(Notification.PRIORITY_MAX)
+                .setDeleteIntent(dismissPending);
+
+        Map<String, String> status = device.getStatus();
+        if (status != null && status.containsKey(ComposeActivity.EXTRA_MESSAGE_PATH)) {
+            Intent pullIntent = new Intent(this, ComposeActivity.class);
+            pullIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            pullIntent.putExtra(ComposeActivity.EXTRA_MESSAGE_PATH, status.get(ComposeActivity.EXTRA_MESSAGE_PATH));
+
+            notificationBuilder.addAction(new Notification.Action.Builder(R.drawable.ic_cast_black_24dp, "Pull Message", PendingIntent.getActivity(this, 0, pullIntent, PendingIntent.FLAG_CANCEL_CURRENT)).build());
+        }
+
+        Notification notification = notificationBuilder.build();
+        mNotificationManager.notify(FOCUS_NOTIFICATION, notification);
+    }
+
+
+    public PermissionManager getPermissionManager() {
+        return mPermissionManager;
+    }
+
+    public String getDeviceId() {
+        return mDeviceId;
+    }
+
+    public Map<String, DeviceData> getDiscovered() {
+        return mDiscovered;
+    }
+
+    public void setStatus(String key, String value) {
+        mDevicesReference.child(mDeviceId).child("status").child(key).child(value);
+    }
+
+    public FirebaseDatabase getFirebaseDB() {
+        return mFirebaseDB;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new PermissionServiceBinder();
+    }
+
+    void initForegroundNotification() {
+
+        Intent contentIntent = new Intent(this, PermissionService.class);
+        PendingIntent contentPendingIntent = PendingIntent.getActivity(this, 0, contentIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        Intent discoverIntent = new Intent(getApplicationContext(), PermissionService.class);
+        discoverIntent.putExtra("type", "discover");
+        PendingIntent discoverPendingIntent = PendingIntent.getService(this, 1, discoverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        Intent closeIntent = new Intent(getApplicationContext(), PermissionService.class);
+        closeIntent.putExtra("type", "close");
+        PendingIntent closePendingIntent = PendingIntent.getService(this, 2, closeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        Notification notification = new Notification.Builder(this)
+                .setContentIntent(contentPendingIntent)
+                .setSmallIcon(R.drawable.ic_vpn_key_black_24dp)
+                .setContentTitle("Permission service running")
+                .addAction(new Notification.Action.Builder(R.drawable.ic_zoom_in_black_24dp, "Discover", discoverPendingIntent).build())
+                .addAction(new Notification.Action.Builder(R.drawable.ic_close_black_24dp, "Stop", closePendingIntent).build())
+                .build();
+        startForeground(FOREGROUND_NOTIFICATION_ID, notification);
+    }
+
+    void refreshForegroundNotification(Notification notification) {
+        mNotificationManager.notify(FOREGROUND_NOTIFICATION_ID, notification);
+    }
+
+
+    public void initDeviceBlessing() {
+
+        final DatabaseReference deviceBlessingRef = mFirebaseDB.getReference(KEY_BLESSINGS).child(mDeviceId);
+        deviceBlessingRef.addListenerForSingleValueEvent(new ValueEventListener() {
+            @Override
+            public void onDataChange(DataSnapshot dataSnapshot) {
+                if (dataSnapshot.exists()) {
+                    mDeviceBlessing = new Blessing(dataSnapshot);
+                } else {
+                    mDeviceBlessing = new Blessing(mDeviceId, null, deviceBlessingRef);
+                }
+
+                //give device access its own document directory
+                mDeviceBlessing.setPermissions("documents/" + mDeviceId, PermissionManager.FLAG_WRITE | PermissionManager.FLAG_READ);
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+
+            }
+        });
+    }
+
+    public Blessing getDeviceBlessing() {
+        return mDeviceBlessing;
+    }
+
+    public void initMessenger() {
+        mMessengerReference = mFirebaseDB.getReference("messages");
+        mMessenger = new Messenger(mDeviceId, mMessengerReference);
+
+        mMessenger.on("disassociate", new Messenger.Listener() {
+            @Override
+            public void call(String args, Messenger.Ack callback) {
+
+            }
+        });
+
+        mMessenger.on("cast", new Messenger.Listener() {
+            @Override
+            public void call(String args, Messenger.Ack callback) {
+                if (args != null) {
+                    try {
+                        JSONObject jsonArgs = new JSONObject(args);
+                        if (jsonArgs.has("activity")) {
+                            if (ComposeActivity.class.getSimpleName().equals(jsonArgs.getString("activity"))) {
+                                String path = jsonArgs.getString(ComposeActivity.EXTRA_MESSAGE_PATH);
+                                Intent emailIntent = new Intent(PermissionService.this, ComposeActivity.class);
+                                emailIntent.putExtra(ComposeActivity.EXTRA_MESSAGE_PATH, path);
+                                emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                                startActivity(emailIntent);
+                            }
+                        }
+                    } catch (JSONException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        });
+    }
+
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent != null && intent.hasExtra("type")) {
+            String type = intent.getStringExtra("type");
+            l("start command " + type);
+
+            //TODO: move to a broadcast receiver. StartService intents are not ideal.
+            if ("sendRequest".equals(type)) {
+                if (intent.hasExtra("request")) {
+                    Message request = intent.getParcelableExtra("request");
+//                    sendRequest(request);
+                }
+
+            } else if ("discover".equals(type)) {
+                if (mDiscovered != null) {
+
+                    Intent discoveryIntent = new Intent(this, DevicePickerActivity.class);
+                    discoveryIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(discoveryIntent);
+                }
+            } else if ("dismiss".equals(type)) {
+                Message request = new Message("disassociate");
+                request.setTarget(mFocus);
+//                sendRequest(request);
+                setFocus(null);
+
+
+            } else if ("close".equals(type)) {
+                stopSelf();
+            } else if ("focus".equals(type)) {
+                if (intent.hasExtra("deviceId")) {
+                    String dId = intent.getStringExtra("deviceId");
+                    l("targetting " + dId);
+                    if (mDiscovered.containsKey(dId)) {
+                        mFocus = dId;
+                        DeviceData target = mDiscovered.get(dId);
+
+                        String title = "Targetting device: " + target.getName();
+
+                        Intent contentIntent = new Intent(this, EmailActivity.class);
+
+                        Intent discoverIntent = new Intent(this, PermissionService.class);
+                        discoverIntent.putExtra("type", "discover");
+                        PendingIntent discoverPendingIntent = PendingIntent.getService(this, 0, discoverIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+                        Intent castIntent = new Intent(this, PermissionService.class);
+                        castIntent.putExtra("type", "sendRequest");
+                        castIntent.putExtra("request", new Message("start"));
+                        PendingIntent castPendingIntent = PendingIntent.getService(this, 0, castIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+                        Notification notification = new Notification.Builder(this)
+                                .setPriority(Notification.PRIORITY_HIGH)
+                                .setVibrate(new long[]{100})
+                                .setContentIntent(PendingIntent.getActivity(this, 0, contentIntent, 0))
+                                .setSmallIcon(R.drawable.ic_vpn_key_black_24dp)
+                                .setContentTitle(title)
+                                .addAction(new Notification.Action.Builder(R.drawable.ic_cast_black_24dp, "Cast", castPendingIntent).build())
+                                .addAction(new Notification.Action.Builder(R.drawable.ic_zoom_in_black_24dp, "Discover", discoverPendingIntent).build())
+                                .build();
+
+                        refreshForegroundNotification(notification);
+
+                    }
+                    for (Iterator<Integer> iterator = mDiscoveredNotifications.values().iterator(); iterator.hasNext(); ) {
+                        int notId = iterator.next();
+                        mNotificationManager.cancel(notId);
+                    }
+                    mDiscoveredNotifications.clear();
+                }
+            }
+        }
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    void registerDevice() {
+
+        mLocalDeviceReference = mDevicesReference.child(mDeviceId);
+        mLocalDeviceReference.addValueEventListener(new ValueEventListener() {
+            @Override
+            public void onDataChange(DataSnapshot dataSnapshot) {
+                if (!dataSnapshot.exists()) {
+                    resetLocalDevice();
+                } else {
+                    try {
+                        mLocalDevice = dataSnapshot.getValue(DeviceData.class);
+
+                    } catch (DatabaseException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+
+            }
+        });
+
+
+    }
+
+    private DeviceData mLocalDevice;
+
+    void resetLocalDevice() {
+        final String deviceName = android.os.Build.MODEL;
+        mLocalDevice = new DeviceData(mDeviceId, deviceName);
+        mLocalDevice.setActive(true);
+        mLocalDeviceReference.setValue(mLocalDevice);
+    }
+
+    void initDiscovery() {
+        mDevicesReference.addChildEventListener(new ChildEventListener() {
+            @Override
+            public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+                updateDevice(dataSnapshot);
+            }
+
+            @Override
+            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+                updateDevice(dataSnapshot);
+            }
+
+            @Override
+            public void onChildRemoved(DataSnapshot dataSnapshot) {
+
+            }
+
+            @Override
+            public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+
+            }
+        });
+    }
+
+    public void addDiscoveryListener(DiscoveryListener listener) {
+        mDiscoveryListener.add(listener);
+    }
+
+    private void updateDevice(DataSnapshot dataSnapshot) {
+        if (dataSnapshot.exists()) {
+            String key = dataSnapshot.getKey();
+            if (!mDeviceId.equals(key)) {
+                try {
+                    DeviceData device = dataSnapshot.getValue(DeviceData.class);
+                    if (device != null) {
+                        mDiscovered.put(key, device);
+                        for (DiscoveryListener listener : mDiscoveryListener) {
+                            listener.onChange(mDiscovered);
+                        }
+                    }
+                } catch (DatabaseException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+
+    public static void start(Context context) {
+        context.startService(new Intent(context, PermissionService.class));
+    }
+
+    //convenience class for when context implements ServiceConnection
+    //throws cast exception
+    public static void bind(Context context) {
+        ServiceConnection connection = (ServiceConnection) context;
+        bind(context, connection);
+    }
+
+    public static void bind(Context context, ServiceConnection connection) {
+        context.bindService(new Intent(context, PermissionService.class), connection, BIND_AUTO_CREATE);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DeviceData.java b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DeviceData.java
new file mode 100644
index 0000000..697561b
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DeviceData.java
@@ -0,0 +1,58 @@
+// Copyright 2016 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 examples.baku.io.permissions.discovery;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by phamilton on 6/19/16.
+ */
+public class DeviceData {
+
+    private String id;
+    private String name;
+    private Boolean active;
+    private Map<String, String> status = new HashMap<>();
+
+    public DeviceData(){}
+
+    public DeviceData(String id, String name){
+        this.id = id;
+        this.name = name;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void setActive(boolean active) {
+        this.active = active;
+    }
+
+    public Map<String, String> getStatus() {
+        return status;
+    }
+
+    public void setStatus(Map<String, String> status) {
+        this.status = status;
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivity.java b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivity.java
new file mode 100644
index 0000000..0ace780
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivity.java
@@ -0,0 +1,117 @@
+// Copyright 2016 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 examples.baku.io.permissions.discovery;
+
+import android.app.Fragment;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.v7.app.AppCompatActivity;
+
+import java.util.Map;
+
+import examples.baku.io.permissions.PermissionService;
+import examples.baku.io.permissions.R;
+import examples.baku.io.permissions.examples.ComposeActivity;
+import examples.baku.io.permissions.util.EventFragment;
+
+public class DevicePickerActivity extends AppCompatActivity implements EventFragment.EventFragmentListener, ServiceConnection {
+
+    private PermissionService mPermissionService;
+    private Map<String, DeviceData> mDevices;
+    private DevicePickerActivityFragment mFragment;
+
+    private int requestCode;
+    public static final int REQUEST_FOCUS = -1;
+    public static final int REQUEST_DEVICE_ID = 2;
+    public static final String EXTRA_REQUEST = "requestCode";
+    public static final String EXTRA_DEVICE_ID = "requestCode";
+    public static final String EXTRA_REQUEST_ARGS = "requestArgs";
+
+    private Intent mIntent;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));  //close notification tray
+        setContentView(R.layout.content_device_picker);
+
+        Intent intent = getIntent();
+        if(intent != null){
+            requestCode = intent.getIntExtra(EXTRA_REQUEST, REQUEST_FOCUS);
+        }
+
+        mIntent = getIntent();
+        PermissionService.bind(this);
+    }
+
+    @Override
+    public void onAttachFragment(Fragment fragment) {
+        super.onAttachFragment(fragment);
+        mFragment = (DevicePickerActivityFragment)fragment;
+        mFragment.setDevices(mDevices);
+    }
+
+    @Override
+    public boolean onFragmentEvent(int action, Bundle args, EventFragment fragment) {
+        switch(action){
+            case DevicePickerActivityFragment.EVENT_ITEMCLICKED:
+                String dId = args.getString(DevicePickerActivityFragment.ARG_DEVICE_ID);
+                if(dId != null){
+                    if(requestCode == REQUEST_DEVICE_ID){
+                        Intent result = new Intent();
+                        result.putExtra(EXTRA_DEVICE_ID,dId);
+                        if(mIntent != null && mIntent.hasExtra(EXTRA_REQUEST_ARGS))
+                        {
+                            result.putExtra(EXTRA_REQUEST_ARGS, mIntent.getStringExtra(EXTRA_REQUEST_ARGS));
+                        }
+                        setResult(0, result);
+                    }else{
+                        mPermissionService.setFocus(dId);
+                    }
+                    finish();
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        mPermissionService = ((PermissionService.PermissionServiceBinder)service).getInstance();
+        mDevices = mPermissionService.getDiscovered();
+        if(mFragment != null){
+            mFragment.setDevices(mDevices);
+        }
+        mPermissionService.addDiscoveryListener(new PermissionService.DiscoveryListener() {
+            @Override
+            public void onChange(Map<String, DeviceData> devices) {
+                if(mFragment != null){
+                    mFragment.setDevices(mDevices);
+                }
+            }
+
+            @Override
+            public void onDisassociate(String deviceId) {
+
+            }
+        });
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unbindService(this);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivityFragment.java b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivityFragment.java
new file mode 100644
index 0000000..6b2bcd4
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/discovery/DevicePickerActivityFragment.java
@@ -0,0 +1,126 @@
+// Copyright 2016 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 examples.baku.io.permissions.discovery;
+
+import android.os.Bundle;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import examples.baku.io.permissions.R;
+import examples.baku.io.permissions.util.EventFragment;
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+public class DevicePickerActivityFragment extends EventFragment {
+
+    public static final int EVENT_ITEMCLICKED = 2;
+
+    public static final String ARG_DEVICE_ID = "deviceId";
+
+    LinkedHashMap<String,DeviceData> devices = new LinkedHashMap<>();
+    DeviceListAdapter mAdapter;
+    RecyclerView mDeviceRecycler;
+    LinearLayoutManager mLayoutManager;
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.fragment_device_picker, container, false);
+        mAdapter = new DeviceListAdapter();
+        mLayoutManager = new LinearLayoutManager(getActivity());
+        mDeviceRecycler = (RecyclerView) view.findViewById(R.id.deviceRecyclerView);
+        mDeviceRecycler.setLayoutManager(mLayoutManager);
+        mDeviceRecycler.setAdapter(mAdapter);
+        return view;
+    }
+
+    public void setDevices(Map<String,DeviceData> devices){
+        if(devices != null){
+            this.devices = new LinkedHashMap<>(devices);
+        }else{
+            this.devices.clear();
+        }
+        if(mAdapter != null){
+            this.mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+        public CardView mCardView;
+        public ViewHolder(CardView v) {
+            super(v);
+            mCardView = v;
+
+            mCardView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+
+                }
+            });
+        }
+    }
+
+    class DeviceListAdapter extends RecyclerView.Adapter<ViewHolder> {
+
+        public DeviceData getItem(int position) {
+            List<String> order = new ArrayList<>(devices.keySet());
+            return devices.get(order.get(position));
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent,
+                                             int viewType) {
+            // create a new view
+            CardView v = (CardView) LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.device_card_item, parent, false);
+            // set the view's size, margins, paddings and layout parameters
+            ViewHolder vh = new ViewHolder(v);
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+
+            final DeviceData item = getItem(position);
+
+            String title = item.getName();
+            if (title != null) {
+                TextView titleView = (TextView) holder.mCardView.findViewById(R.id.card_title);
+                titleView.setText(title);
+            }
+
+            holder.mCardView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Bundle args = new Bundle();
+                    args.putString(ARG_DEVICE_ID, item.getId());
+                    onEvent(EVENT_ITEMCLICKED, args);
+//                    Intent intent = new Intent(EmailActivity.this, ComposeActivity.class);
+//                    intent.putExtra(ComposeActivity.EXTRA_MESSAGE_ID, item.getId());
+//                    startActivityForResult(intent, 0);
+                }
+            });
+
+        }
+
+        @Override
+        public int getItemCount() {
+            return devices.size();
+        }
+    }
+
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/examples/ComposeActivity.java b/permissions/app/src/main/java/examples/baku/io/permissions/examples/ComposeActivity.java
new file mode 100644
index 0000000..a8b23cc
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/examples/ComposeActivity.java
@@ -0,0 +1,333 @@
+// Copyright 2016 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 examples.baku.io.permissions.examples;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.TextInputLayout;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import examples.baku.io.permissions.PermissionManager;
+import examples.baku.io.permissions.discovery.DeviceData;
+import examples.baku.io.permissions.PermissionService;
+import examples.baku.io.permissions.R;
+import examples.baku.io.permissions.discovery.DevicePickerActivity;
+import examples.baku.io.permissions.synchronization.SyncText;
+
+public class ComposeActivity extends AppCompatActivity implements ServiceConnection {
+
+    public final static String EXTRA_MESSAGE_ID = "messageId";
+    public final static String EXTRA_MESSAGE_PATH = "messagePath";
+
+    private String mPath;
+
+    private String mOwner;
+    private String mDeviceId;
+    private String mId;
+    private PermissionService mPermissionService;
+    private DatabaseReference mMessageRef;
+    private DatabaseReference mSyncedMessageRef;
+
+    String sourceId;
+
+
+    EditText mToText;
+    EditText mFrom;
+    EditText mSubject;
+    EditText mMessage;
+
+    TextInputLayout mToLayout;
+    TextInputLayout mFromLayout;
+    TextInputLayout mSubjectLayout;
+    TextInputLayout mMessageLayout;
+
+
+    Map<String, Integer> permissions = new HashMap<>();
+
+    HashMap<String, SyncText> syncTexts = new HashMap<>();
+
+    HashMap<String, ValueEventListener> listeners = new HashMap<>();
+    HashMap<String, DataSnapshot> mSnapshots = new HashMap<>();
+    DataSnapshot currentSnapshot;
+
+    String original;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_compose);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        toolbar.setTitle("Compose Message");
+
+
+        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        fab.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                sendMessage();
+            }
+        });
+
+
+        mToText = (EditText) findViewById(R.id.composeTo);
+        mToLayout = (TextInputLayout) findViewById(R.id.composeToLayout);
+
+        mFrom = (EditText) findViewById(R.id.composeFrom);
+        mFromLayout = (TextInputLayout) findViewById(R.id.composeFromLayout);
+
+        mSubject = (EditText) findViewById(R.id.composeSubject);
+        mSubjectLayout = (TextInputLayout) findViewById(R.id.composeSubjectLayout);
+
+        mMessage = (EditText) findViewById(R.id.composeMessage);
+        mMessageLayout = (TextInputLayout) findViewById(R.id.composeMessageLayout);
+
+        bindService(new Intent(this, PermissionService.class), this, Service.BIND_AUTO_CREATE);
+    }
+
+
+    @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_compose, 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_send) {
+            sendMessage();
+        } else if (id == R.id.action_cast) {
+            if (mPermissionService != null) {
+                    Intent requestIntent = new Intent(ComposeActivity.this, DevicePickerActivity.class);
+                    requestIntent.putExtra(DevicePickerActivity.EXTRA_REQUEST, DevicePickerActivity.REQUEST_DEVICE_ID);
+                    requestIntent.putExtra(DevicePickerActivity.EXTRA_REQUEST_ARGS, mPath);
+                    startActivityForResult(requestIntent, DevicePickerActivity.REQUEST_DEVICE_ID);
+            }
+
+        } else if (id == R.id.action_settings) {
+
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+
+    void sendMessage() {
+
+        //TODO: PermissionManager.request()
+
+        finish();
+    }
+
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == DevicePickerActivity.REQUEST_DEVICE_ID && data != null && data.hasExtra(DevicePickerActivity.EXTRA_DEVICE_ID)) {
+            String focus = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ID);
+            mPermissionService.getPermissionManager().bless(focus)
+                    .setPermissions(mPath, PermissionManager.FLAG_READ)
+                    .setPermissions(mPath + "/message", PermissionManager.FLAG_WRITE)
+                    .setPermissions(mPath + "/subject", PermissionManager.FLAG_WRITE);
+            JSONObject castArgs = new JSONObject();
+            try {
+                castArgs.put("activity", ComposeActivity.class.getSimpleName());
+                castArgs.put(EXTRA_MESSAGE_PATH, mPath);
+                mPermissionService.getMessenger().to(focus).emit("cast", castArgs.toString());
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        PermissionService.PermissionServiceBinder binder = (PermissionService.PermissionServiceBinder) service;
+        mPermissionService = binder.getInstance();
+
+
+        if (mPermissionService != null) {
+            mDeviceId = mPermissionService.getDeviceId();
+
+            Intent intent = getIntent();
+            if (intent != null) {
+                if (intent.hasExtra(EXTRA_MESSAGE_PATH)) {
+                    mPath = intent.getStringExtra(EXTRA_MESSAGE_PATH);
+                    String[] pathElements = mPath.split("/");
+                    mId = pathElements[pathElements.length - 1];
+                } else if (intent.hasExtra(EXTRA_MESSAGE_ID)) {
+                    mId = intent.getStringExtra(EXTRA_MESSAGE_ID);
+                    mPath = EmailActivity.KEY_DOCUMENTS
+                            + "/" + mDeviceId
+                            + "/" + EmailActivity.KEY_EMAILS
+                            + "/" + EmailActivity.KEY_MESSAGES
+                            + "/" + mId;
+                }
+            }
+
+            if (mPath == null) {
+                mId = UUID.randomUUID().toString();
+                mPath = "documents/" + mDeviceId + "/emails/messages/" + mId;
+            }
+
+            mMessageRef = mPermissionService.getFirebaseDB().getReference(mPath);
+            mSyncedMessageRef = mMessageRef.child("syncedValues");
+            mPermissionService.getPermissionManager().addPermissionEventListener(mPath, messagePermissionListener);
+            wrapTextField(mToLayout, "to");
+            wrapTextField(mFromLayout, "from");
+            wrapTextField(mSubjectLayout, "subject");
+            wrapTextField(mMessageLayout, "message");
+
+            mPermissionService.setStatus(EXTRA_MESSAGE_PATH, mPath);
+
+        }
+    }
+
+    PermissionManager.OnPermissionChangeListener messagePermissionListener = new PermissionManager.OnPermissionChangeListener() {
+        @Override
+        public void onPermissionChange(int current) {
+            if (current > 0) {
+                mMessageRef.child("id").setValue(mId);
+            }
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+
+    }
+
+    void wrapTextField(final TextInputLayout editContainer, final String key) {
+        final EditText edit = editContainer.getEditText();
+
+        mPermissionService.getPermissionManager().addPermissionEventListener(mPath + "/" + key, new PermissionManager.OnPermissionChangeListener() {
+            @Override
+            public void onPermissionChange(int current) {
+                Log.e(key, "::" + current);
+                if ((current & PermissionManager.FLAG_WRITE) == PermissionManager.FLAG_WRITE) {
+                    edit.setEnabled(true);
+                    edit.setOnClickListener(null);
+                    edit.setFocusable(true);
+                    edit.setBackgroundColor(Color.TRANSPARENT);
+                    linkTextField(edit, key);
+                } else if ((current & PermissionManager.FLAG_READ) == PermissionManager.FLAG_READ) {
+                    edit.setEnabled(false);
+                    edit.setOnClickListener(null);
+                    edit.setFocusable(false);
+                    edit.setBackgroundColor(Color.TRANSPARENT);
+                    linkTextField(edit, key);
+                } else {
+                    unlinkTextField(key);
+                    edit.setEnabled(false);
+                    edit.setFocusable(false);
+                    edit.setBackgroundColor(Color.BLACK);
+                }
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+
+            }
+        });
+    }
+
+    void unlinkTextField(String key) {
+        if (syncTexts.containsKey(key)) {
+            syncTexts.get(key).unlink();
+        }
+    }
+
+    void linkTextField(final EditText edit, final String key) {
+        final SyncText syncText = new SyncText(mSyncedMessageRef.child(key), mMessageRef.child(key));
+        syncTexts.put(key, syncText);
+
+        syncText.setOnTextChangeListener(new SyncText.OnTextChangeListener() {
+            @Override
+            public void onTextChange(final String currentText) {
+                final int sel = Math.min(edit.getSelectionStart(), currentText.length());
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        edit.setText(currentText);
+                        if (sel > -1) {
+                            edit.setSelection(sel);
+                        }
+                    }
+                });
+            }
+        });
+
+        edit.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                syncText.update(s.toString());
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+
+            }
+        });
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unlinkTextField("to");
+        unlinkTextField("form");
+        unlinkTextField("subject");
+        unlinkTextField("message");
+        if (mPermissionService != null) {
+            mPermissionService.revokeAll();
+        }
+        unbindService(this);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/examples/EmailActivity.java b/permissions/app/src/main/java/examples/baku/io/permissions/examples/EmailActivity.java
new file mode 100644
index 0000000..db2a7f4
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/examples/EmailActivity.java
@@ -0,0 +1,351 @@
+// Copyright 2016 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 examples.baku.io.permissions.examples;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.ContactsContract;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.common.collect.Iterables;
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseException;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.ValueEventListener;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import examples.baku.io.permissions.PermissionService;
+import examples.baku.io.permissions.R;
+import examples.baku.io.permissions.discovery.DevicePickerActivity;
+
+public class EmailActivity extends AppCompatActivity implements ServiceConnection {
+
+
+    private static final String TAG = PermissionService.class.getSimpleName();
+
+    static void l(String msg) {
+        Log.e(TAG, msg);
+    }   //TODO: real logging
+
+    public static final String KEY_DOCUMENTS = "documents";
+    public static final String KEY_EMAILS = "emails";
+    public static final String KEY_MESSAGES = "messages";
+
+    private PermissionService mPermissionService;
+    private String mDeviceId;
+    private FirebaseDatabase mFirebaseDB;
+    private DatabaseReference mMessagesRef;
+
+    private RecyclerView mInboxRecyclerView;
+    private MessagesAdapter mInboxAdapter;
+    private LinearLayoutManager mLayoutManager;
+
+    private LinkedHashMap<String, MessageData> mMessages = new LinkedHashMap<>();
+
+    private ArrayList<String> mMessageOrder = new ArrayList<>();
+
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_permission);
+        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+
+        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
+        if (fab != null) {
+            fab.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View view) {
+                    startActivity(new Intent(EmailActivity.this, ComposeActivity.class));
+//                    mPermissionService.getDeviceBlessing().setPermissions("documents/" + mDeviceId +"/snake", new Random().nextInt());
+
+                }
+            });
+        }
+
+        PermissionService.start(this);
+        PermissionService.bind(this);
+
+        mInboxAdapter = new MessagesAdapter(mMessages);
+        mLayoutManager = new LinearLayoutManager(this);
+        mInboxRecyclerView = (RecyclerView) findViewById(R.id.inboxRecyclerView);
+        mInboxRecyclerView.setLayoutManager(mLayoutManager);
+        mInboxRecyclerView.setAdapter(mInboxAdapter);
+
+        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback);
+
+        itemTouchHelper.attachToRecyclerView(mInboxRecyclerView);
+
+    }
+
+    @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_permission, 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_cast) {
+
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT) {
+        @Override
+        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
+            return false;
+        }
+
+        @Override
+        public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {
+            //Remove swiped item from list and notify the RecyclerView
+            int pos = viewHolder.getAdapterPosition();
+            MessageData item = mInboxAdapter.getItem(pos);
+            if (item != null) {
+                mMessagesRef.child(item.getId()).removeValue();
+            }
+        }
+    };
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        PermissionService.PermissionServiceBinder binder = (PermissionService.PermissionServiceBinder) service;
+        mPermissionService = binder.getInstance();
+        if (mPermissionService != null) {
+            mDeviceId = mPermissionService.getDeviceId();
+            mFirebaseDB = mPermissionService.getFirebaseDB();
+            mMessagesRef = mFirebaseDB.getReference(KEY_DOCUMENTS).child(mDeviceId).child(KEY_EMAILS).child(KEY_MESSAGES);
+            mMessagesRef.addValueEventListener(messagesValueListener);
+            mMessagesRef.addChildEventListener(messageChildListener);
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+
+    }
+
+
+    private ChildEventListener messageChildListener = new ChildEventListener() {
+        @Override
+        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+            onMessageUpdated(dataSnapshot);
+            mInboxAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+            onMessageUpdated(dataSnapshot);
+            mInboxAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onChildRemoved(DataSnapshot dataSnapshot) {
+            onMessageRemoved(dataSnapshot.getKey());
+            mInboxAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    private ValueEventListener messagesValueListener = new ValueEventListener() {
+        @Override
+        public void onDataChange(DataSnapshot dataSnapshot) {
+            onMessagesUpdated(dataSnapshot);
+            mInboxAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    void onMessagesUpdated(DataSnapshot snapshot) {
+        if (snapshot == null) throw new IllegalArgumentException("null snapshot");
+
+        mMessages.clear();
+        for (DataSnapshot snap : snapshot.getChildren()) {
+            onMessageUpdated(snap);
+        }
+    }
+
+    void onMessageUpdated(DataSnapshot snapshot) {
+        try {
+            MessageData msg = snapshot.getValue(MessageData.class);
+            String key = msg.getId();
+            mMessages.put(key, msg);
+        } catch (DatabaseException e) {
+            e.printStackTrace();
+        }
+    }
+
+    void onMessageRemoved(String id) {
+        mMessages.remove(id);
+    }
+
+
+    public static class ViewHolder extends RecyclerView.ViewHolder {
+        public CardView mCardView;
+
+        public ViewHolder(CardView v) {
+            super(v);
+            mCardView = v;
+
+            mCardView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+
+                }
+            });
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == DevicePickerActivity.REQUEST_DEVICE_ID && data != null && data.hasExtra(DevicePickerActivity.EXTRA_DEVICE_ID)) {
+            String focus = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ID);
+            String path = data.getStringExtra(DevicePickerActivity.EXTRA_REQUEST_ARGS);
+
+            mPermissionService.getPermissionManager().bless(focus)
+                    .setPermissions(path, 3);
+            JSONObject castArgs = new JSONObject();
+            try {
+                castArgs.put("activity", ComposeActivity.class.getSimpleName());
+                castArgs.put(ComposeActivity.EXTRA_MESSAGE_PATH, path);
+                mPermissionService.getMessenger().to(focus).emit("cast", castArgs.toString());
+            } catch (JSONException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public class MessagesAdapter extends RecyclerView.Adapter<ViewHolder> {
+        private LinkedHashMap<String, MessageData> mDataset;
+
+        public MessagesAdapter(LinkedHashMap<String, MessageData> dataset) {
+            setDataset(dataset);
+        }
+
+        public void setDataset(LinkedHashMap<String, MessageData> mDataset) {
+            this.mDataset = mDataset;
+        }
+
+        public MessageData getItem(int position) {
+            return Iterables.get(mDataset.values(), position);
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent,
+                                             int viewType) {
+            // create a new view
+            CardView v = (CardView) LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.inbox_card_item, parent, false);
+            // set the view's size, margins, paddings and layout parameters
+            ViewHolder vh = new ViewHolder(v);
+            return vh;
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+
+            final MessageData item = getItem(position);
+
+            String title = item.getFrom();
+            if (title != null) {
+                TextView titleView = (TextView) holder.mCardView.findViewById(R.id.card_title);
+                titleView.setText(item.getFrom());
+                TextView subtitleView = (TextView) holder.mCardView.findViewById(R.id.card_subtitle);
+                subtitleView.setText(item.getSubject());
+            }
+
+            ImageView castButton = (ImageView) holder.mCardView.findViewById(R.id.card_trailing);
+            castButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    //choose device
+                    Intent requestIntent = new Intent(EmailActivity.this, DevicePickerActivity.class);
+                    String path = EmailActivity.KEY_DOCUMENTS
+                            + "/" + mDeviceId
+                            + "/" + EmailActivity.KEY_EMAILS
+                            + "/" + EmailActivity.KEY_MESSAGES
+                            + "/" + item.getId();
+                    requestIntent.putExtra(DevicePickerActivity.EXTRA_REQUEST, DevicePickerActivity.REQUEST_DEVICE_ID);
+                    requestIntent.putExtra(DevicePickerActivity.EXTRA_REQUEST_ARGS, path);
+                    startActivityForResult(requestIntent, DevicePickerActivity.REQUEST_DEVICE_ID);
+                }
+            });
+
+            holder.mCardView.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    Intent intent = new Intent(EmailActivity.this, ComposeActivity.class);
+                    intent.putExtra(ComposeActivity.EXTRA_MESSAGE_ID, item.getId());
+                    startActivityForResult(intent, 0);
+                }
+            });
+
+        }
+
+        @Override
+        public int getItemCount() {
+            return mDataset.size();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unbindService(this);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/examples/InboxFragment.java b/permissions/app/src/main/java/examples/baku/io/permissions/examples/InboxFragment.java
new file mode 100644
index 0000000..fe2a764
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/examples/InboxFragment.java
@@ -0,0 +1,33 @@
+// Copyright 2016 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 examples.baku.io.permissions.examples;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import examples.baku.io.permissions.R;
+import examples.baku.io.permissions.util.EventFragment;
+
+/**
+ * A placeholder fragment containing a simple view.
+ */
+public class InboxFragment extends EventFragment {
+
+    public InboxFragment(){}
+
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.inbox_list, container, false);
+
+
+        return view;
+    }
+
+
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/examples/MessageData.java b/permissions/app/src/main/java/examples/baku/io/permissions/examples/MessageData.java
new file mode 100644
index 0000000..12d2793
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/examples/MessageData.java
@@ -0,0 +1,83 @@
+// Copyright 2016 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 examples.baku.io.permissions.examples;
+
+import com.google.firebase.database.ServerValue;
+
+import java.util.Map;
+
+/**
+ * Created by phamilton on 6/22/16.
+ */
+public class MessageData {
+
+    String id;
+    String to = "";
+    String from = "";
+    String subject = "";
+    String message = "";
+    long timeStamp;
+
+//    Map<String, Map<String, Integer>> shared = new HashMap<>();
+
+    public MessageData(){}
+
+    public MessageData(String id, String to, String from, String subject, String message) {
+        this.id = id;
+        this.to = to;
+        this.from = from;
+        this.subject = subject;
+        this.message = message;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getTo() {
+        return to;
+    }
+
+    public void setTo(String to) {
+        this.to = to != null ? to : "";
+    }
+
+    public String getFrom() {
+        return from;
+    }
+
+    public void setFrom(String from) {
+        this.from = from != null ? from : "";
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject != null ? subject : "";
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message != null ? message : "";
+    }
+
+
+    public Map<String,String> getTimeStamp() {
+        return ServerValue.TIMESTAMP;
+    }
+
+    public void setTimeStamp(long timeStamp) {
+        this.timeStamp = timeStamp;
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Message.java b/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Message.java
new file mode 100644
index 0000000..62eb995
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Message.java
@@ -0,0 +1,142 @@
+// Copyright 2016 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 examples.baku.io.permissions.messenger;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.UUID;
+
+/**
+ * Created by phamilton on 6/19/16.
+ */
+public class Message implements Parcelable{
+    private String id;
+    private String parent;
+    private String type;
+    private String target;
+    private String source;
+    private String message;
+    private boolean callback;
+
+    public Message(){}
+
+    public Message(String type) {
+        this(type, null);
+    }
+
+    public Message(String type, String message){
+        this.id = UUID.randomUUID().toString();
+        this.type = type;
+        this.message = message;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getParent() {
+        return parent;
+    }
+
+    public void setParent(String parent) {
+        this.parent = parent;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public boolean isCallback() {
+        return callback;
+    }
+
+    public void setCallback(boolean callback) {
+        this.callback = callback;
+    }
+
+
+//    public Message getChildInstance(){
+//        Message result = new Message();
+//        result.id = UUID.randomUUID().toString();
+//        result.parent = this.id;
+//        result.type = this.type;
+//        result.target = this.target;
+//        result.source = this.source;
+//        result.message = this.message;
+//        result.callback = this.callback;
+//        return result;
+//    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(id);
+        dest.writeString(type);
+        dest.writeString(target);
+        dest.writeString(message);
+        dest.writeByte((byte) (callback ? 1 : 0));
+    }
+
+    private Message(Parcel in) {
+        this.id = in.readString();
+        this.type = in.readString();
+        this.target = in.readString();
+        this.message = in.readString();
+        this.callback = in.readByte() != 0;
+
+    }
+
+    public static final Creator<Message> CREATOR
+            = new Creator<Message>() {
+        @Override
+        public Message createFromParcel(Parcel source) {
+            return new Message(source);
+        }
+
+        @Override
+        public Message[] newArray(int size) {
+            return new Message[0];
+        }
+
+    };
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Messenger.java b/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Messenger.java
new file mode 100644
index 0000000..faef35f
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/messenger/Messenger.java
@@ -0,0 +1,204 @@
+// Copyright 2016 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 examples.baku.io.permissions.messenger;
+
+import android.provider.ContactsContract;
+import android.widget.NumberPicker;
+
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseException;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.ValueEventListener;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by phamilton on 6/28/16.
+ *
+ * !!!!!!!!!FOR PROTOTYPING ONLY!!!!!!!
+ * Messaging on top of Firebase real-time database.
+ * Handles single target messaging only.
+ * Stand in for something like socket.io.
+ */
+public class Messenger implements ChildEventListener {
+
+    static final String KEY_TARGET = "target";
+
+    static final String KEY_GROUPS = "_groups";
+
+    private String mId;
+    private DatabaseReference mReference;
+
+    //hacky client side grouping
+    private DatabaseReference mGroupsReference;
+    private DataSnapshot mGroups;
+
+    final private Map<String, Listener> mListeners = new HashMap<>();
+    final private Map<String, Ack> mCallbacks = new HashMap<>();
+
+    public Messenger(String id, DatabaseReference reference) {
+        this.mId = id;
+        this.mReference = reference;
+        this.mReference.orderByChild(KEY_TARGET).equalTo(mId).addChildEventListener(this);
+
+        this.mGroupsReference = mReference.child(KEY_GROUPS);
+        this.mGroupsReference.addValueEventListener(groupsListener);
+    }
+
+    public Emitter to(final String target){
+        return new Emitter() {
+            @Override
+            public void emit(String event, String msg, Ack callback) {
+                if(event == null) throw new IllegalArgumentException("event argument can't be null.");
+
+                Message message = new Message(event, msg);
+                message.setSource(mId);
+
+                if(callback != null){
+                    mCallbacks.put(message.getId(), callback);
+                    message.setCallback(true);
+                }
+
+                if(mGroups == null || mGroups.hasChild(target)){
+                    message.setTarget(target);
+                    mReference.child(message.getId()).setValue(message);
+                }else if(mGroups.exists()){
+//                        DataSnapshot members = mGroups.child(target);
+//                        if(members.exists()){
+//                            for(Iterator<DataSnapshot> iterator = members.getChildren().iterator(); iterator.hasNext();){
+//                                String subTarget = iterator.next().getKey();
+//                                Message childMessage = message.getChildInstance();
+//                                childMessage.setTarget(subTarget);
+//                                mReference.child(childMessage.getId()).setValue(childMessage);
+//                            }
+//                        }
+                    }
+            }
+        };
+    }
+
+    public void on(String event, Listener listener){
+        if(listener == null){//remove current
+            off(event);
+        }else{
+            mListeners.put(event, listener);
+        }
+    }
+
+    public void off(String event){
+        if(mListeners.containsKey(event)){
+            mListeners.remove(event);
+        }
+    }
+
+    public void join(String group){
+        mGroupsReference.child(group).child(mId).setValue(0);
+    }
+
+    public void leave(String group){
+        mGroupsReference.child(group).child(mId).removeValue();
+    }
+
+    ValueEventListener groupsListener = new ValueEventListener() {
+        @Override
+        public void onDataChange(DataSnapshot dataSnapshot) {
+
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+
+    @Override
+    public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+        Message message = null;
+        try{
+            message = dataSnapshot.getValue(Message.class);
+
+        }catch(DatabaseException e){
+            e.printStackTrace();
+        }
+
+        if(message!= null){
+            handleMessage(message);
+        }
+
+        //remove from database.
+        dataSnapshot.getRef().removeValue();
+    }
+
+    private boolean handleMessage(final Message message) {
+        String event = message.getType();
+        if(mListeners.containsKey(event)){
+            Ack callback = null;
+            if(message.isCallback()){
+                //route response to sending messenger
+                callback = new Ack() {
+                    @Override
+                    public void call(String args) {
+                        to(message.getSource()).emit(message.getId(), args);
+                    }
+                };
+            }
+            mListeners.get(event).call(message.getMessage(), callback);
+            return true;
+
+        //assume that none of the event listeners match the uuid of a message
+        }else if(mCallbacks.containsKey(event)){
+            mCallbacks.get(event).call(message.getMessage());
+            mCallbacks.remove(event);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+
+    }
+
+    @Override
+    public void onChildRemoved(DataSnapshot dataSnapshot) {
+
+    }
+
+    @Override
+    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+    }
+
+    @Override
+    public void onCancelled(DatabaseError databaseError) {
+        databaseError.toException().printStackTrace();
+    }
+
+    public void disconnect(){
+        mReference.removeEventListener(this);
+    }
+
+    public abstract class Emitter{
+        public void emit(String event, String msg){
+            emit(event, msg, null);
+        }
+
+        abstract public void emit(String event, String msg, Ack callback);
+    }
+
+    public interface Listener{
+        void call(String args, Ack callback);
+    }
+
+    public interface Ack{
+        void call(String args);
+    }
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncText.java b/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncText.java
new file mode 100644
index 0000000..7cc639c
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncText.java
@@ -0,0 +1,268 @@
+// Copyright 2016 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 examples.baku.io.permissions.synchronization;
+
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseException;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.MutableData;
+import com.google.firebase.database.Transaction;
+import com.google.firebase.database.ValueEventListener;
+
+import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch;
+
+import java.util.LinkedList;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Created by phamilton on 6/24/16.
+ */
+public class SyncText {
+
+    static final String KEY_CURRENT = "current";
+    static final String KEY_TEXT = "value";
+    static final String KEY_VERSION = "version";
+    static final String KEY_PATCHES = "patches";
+    static final String KEY_SUBSCRIBERS = "subscribers";
+
+    private String text = "";
+    private int ver;
+    private String original = text;
+    private BlockingQueue<SyncTextPatch> mPatchQueue;
+
+    private DiffMatchPatch diffMatchPatch = new DiffMatchPatch();
+
+    private DatabaseReference mSyncRef;
+    private DatabaseReference mPatchesRef;
+    private DatabaseReference mOutputRef;
+
+    private PatchConsumer mPatchConsumer;
+
+    private OnTextChangeListener mOnTextChangeListener;
+
+    private String mId;
+
+    private String mInstance;
+
+
+    public SyncText(DatabaseReference reference, DatabaseReference output){
+        if(reference == null) throw new IllegalArgumentException("null reference");
+
+        mInstance = UUID.randomUUID().toString();
+
+        mSyncRef = reference;
+        mOutputRef = output;
+
+        mId = UUID.randomUUID().toString();
+
+        mPatchQueue = new LinkedBlockingQueue<>();
+        mPatchConsumer = new PatchConsumer(mPatchQueue);
+
+        new Thread(mPatchConsumer).start();
+
+        link();
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public int getVer() {
+        return ver;
+    }
+
+    public void setVer(int ver) {
+        this.ver = ver;
+    }
+
+    public void setOnTextChangeListener(OnTextChangeListener onTextChangeListener) {
+        this.mOnTextChangeListener = onTextChangeListener;
+    }
+
+    public void update(String newText){
+        if(mPatchesRef == null){
+            throw new RuntimeException("database connection hasn't been initialized");
+        }
+
+        LinkedList<DiffMatchPatch.Patch> patches = diffMatchPatch.patchMake(text, newText);
+
+        if(patches.size() > 0){
+            String patchString = diffMatchPatch.patchToText(patches);
+            SyncTextPatch patch = new SyncTextPatch();
+            patch.setVer(ver + 1);
+            patch.setPatch(patchString);
+            mPatchesRef.push().setValue(patch);
+        }
+    }
+
+    private void processPatch(SyncTextPatch patch){
+
+        int v = patch.getVer();
+        if(this.ver >= v){  //ignore patches for previous versions
+            return;
+        }
+
+        LinkedList<DiffMatchPatch.Patch> remotePatch = new LinkedList<>(diffMatchPatch.patchFromText(patch.getPatch()));
+        Object[] results = diffMatchPatch.patchApply(remotePatch, this.text);
+        //TODO: check results
+        if(results != null && results.length > 0 && results[0] instanceof String){
+            String patchedString = (String)results[0];
+            this.ver = v;
+            this.text = patchedString;
+            updateCurrent();
+        }
+    }
+
+    private void updateCurrent(){
+        mSyncRef.child(KEY_CURRENT).runTransaction(new Transaction.Handler() {
+            @Override
+            public Transaction.Result doTransaction(MutableData currentData) {
+                if(currentData.getValue() == null){
+                    currentData.child(KEY_TEXT).setValue(text);
+                    currentData.child(KEY_VERSION).setValue(ver);
+                }else{
+                    int latest = currentData.child(KEY_VERSION).getValue(Integer.class);
+                    if(latest > ver){
+                        return Transaction.abort();
+                    }
+                    currentData.child(KEY_TEXT).setValue(text);
+                    currentData.child(KEY_VERSION).setValue(ver);
+                }
+                return Transaction.success(currentData);
+            }
+
+            @Override
+            public void onComplete(DatabaseError databaseError, boolean success, DataSnapshot dataSnapshot) {
+                if(success){
+                    if(mOnTextChangeListener != null){
+                        mOnTextChangeListener.onTextChange(text);
+                    }
+                    if(mOutputRef != null){  //pass successful change to output location
+                        mOutputRef.setValue(text);
+                    }
+                }
+            }
+        });
+    }
+
+    public void link(){
+
+        mSyncRef.child(KEY_SUBSCRIBERS).child(mId).setValue(0);
+
+        mPatchesRef = mSyncRef.child(KEY_PATCHES);
+        mSyncRef.child(KEY_CURRENT).addListenerForSingleValueEvent(new ValueEventListener() {
+            @Override
+            public void onDataChange(DataSnapshot dataSnapshot) {
+                if(dataSnapshot.exists()){
+                    text = dataSnapshot.child(KEY_TEXT).getValue(String.class);
+                    ver = dataSnapshot.child(KEY_VERSION).getValue(Integer.class);
+                }else if(mOutputRef != null){   //check if output ref already has a value
+                    mOutputRef.addListenerForSingleValueEvent(new ValueEventListener() {
+                        @Override
+                        public void onDataChange(DataSnapshot dataSnapshot) {
+                            if(dataSnapshot.exists() && dataSnapshot.getValue() != null){
+                                text = dataSnapshot.getValue(String.class);
+                                original = text;
+                            }
+                            updateCurrent();
+                        }
+                        @Override
+                        public void onCancelled(DatabaseError databaseError) {
+
+                        }
+                    });
+                }else{  //version 0, empty string
+                    updateCurrent();
+                }
+
+//                mPatchesRef.orderByChild(KEY_VERSION).startAt(ver).addChildEventListener(mPatchListener);
+                mPatchesRef.addChildEventListener(mPatchListener);
+
+                if(mOnTextChangeListener != null){
+                    mOnTextChangeListener.onTextChange(text);
+                }
+            }
+
+            @Override
+            public void onCancelled(DatabaseError databaseError) {
+
+            }
+        });
+    }
+
+    public String getOriginal() {
+        return original;
+    }
+
+    private ChildEventListener mPatchListener = new ChildEventListener() {
+        @Override
+        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
+            if(dataSnapshot.exists()){
+                try{
+                    SyncTextPatch patch = dataSnapshot.getValue(SyncTextPatch.class);
+                    if(patch != null){
+                        mPatchQueue.add(patch);
+                    }
+                }catch(DatabaseException e){
+                    e.printStackTrace();
+                }
+            }
+
+        }
+
+        @Override
+        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
+
+        }
+
+        @Override
+        public void onChildRemoved(DataSnapshot dataSnapshot) {
+
+        }
+
+        @Override
+        public void onChildMoved(DataSnapshot dataSnapshot, String s) {
+
+        }
+
+        @Override
+        public void onCancelled(DatabaseError databaseError) {
+
+        }
+    };
+
+    public void unlink(){
+        mSyncRef.child(KEY_PATCHES).removeEventListener(mPatchListener);
+    }
+
+    public interface OnTextChangeListener{
+        void onTextChange(String currentText);
+    }
+
+    private class PatchConsumer implements Runnable {
+        private final BlockingQueue<SyncTextPatch> queue;
+
+        PatchConsumer(BlockingQueue q) { queue = q; }
+        public void run() {
+            try {
+                while (true) { consume(queue.take()); }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        void consume(SyncTextPatch patch) {
+            processPatch(patch);
+        }
+    }
+
+}
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncTextPatch.java b/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncTextPatch.java
new file mode 100644
index 0000000..48ef0b6
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/synchronization/SyncTextPatch.java
@@ -0,0 +1,43 @@
+// Copyright 2016 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 examples.baku.io.permissions.synchronization;
+
+import java.util.concurrent.BlockingQueue;
+
+/**
+ * Created by phamilton on 6/24/16.
+ */
+public class SyncTextPatch{
+    private int ver;
+    private String patch;
+    private String source;
+
+    public SyncTextPatch() {}
+
+    public int getVer() {
+        return ver;
+    }
+
+    public void setVer(int ver) {
+        this.ver = ver;
+    }
+
+    public String getPatch() {
+        return patch;
+    }
+
+    public void setPatch(String patch) {
+        this.patch = patch;
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+}
+
diff --git a/permissions/app/src/main/java/examples/baku/io/permissions/util/EventFragment.java b/permissions/app/src/main/java/examples/baku/io/permissions/util/EventFragment.java
new file mode 100644
index 0000000..6d50328
--- /dev/null
+++ b/permissions/app/src/main/java/examples/baku/io/permissions/util/EventFragment.java
@@ -0,0 +1,39 @@
+// Copyright 2016 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 examples.baku.io.permissions.util;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * Created by phamilton on 6/26/16.
+ *
+ * Fragment that requires the binding context to implement an event handler.
+ */
+public class EventFragment extends Fragment{
+
+    EventFragmentListener mListener;
+
+    public interface EventFragmentListener{
+        boolean onFragmentEvent(int action, Bundle args, EventFragment fragment);
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        try {
+            mListener = (EventFragmentListener) context;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(context.toString() + " must implement EventFragmentListener");
+        }
+    }
+
+    public boolean onEvent(int action, Bundle args){
+        if(mListener == null)
+            return false;
+        return mListener.onFragmentEvent(action, args, this);
+    }
+}
diff --git a/permissions/gradle/wrapper/gradle-wrapper.jar b/permissions/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/permissions/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/permissions/gradle/wrapper/gradle-wrapper.properties b/permissions/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..122a0dc
--- /dev/null
+++ b/permissions/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 28 10:00:20 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
diff --git a/permissions/gradlew b/permissions/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/permissions/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"