examples.baku.io.permissions.examples.ComposeActivity.java Source code

Java tutorial

Introduction

Here is the source code for examples.baku.io.permissions.examples.ComposeActivity.java

Source

// 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.Manifest;
import android.app.Service;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
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 com.joanzapata.iconify.IconDrawable;
import com.joanzapata.iconify.fonts.MaterialIcons;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import examples.baku.io.permissions.Blessing;
import examples.baku.io.permissions.PermissionManager;
import examples.baku.io.permissions.PermissionRequest;
import examples.baku.io.permissions.PermissionService;
import examples.baku.io.permissions.PermissionedTextLayout;
import examples.baku.io.permissions.R;
import examples.baku.io.permissions.discovery.DeviceData;
import examples.baku.io.permissions.discovery.DevicePickerActivity;
import examples.baku.io.permissions.synchronization.SyncText;
import examples.baku.io.permissions.synchronization.SyncTextDiff;
import examples.baku.io.permissions.util.Utils;

public class ComposeActivity extends AppCompatActivity implements ServiceConnection {

    public final static String EXTRA_MESSAGE_ID = "messageId";
    public final static String EXTRA_MESSAGE_PATH = "messagePath";
    private final static int SELECT_ATTACHMENT = 1232;

    private String mPath;

    private String mOwner;
    private String mDeviceId;
    private String mId;
    private PermissionService mPermissionService;
    private PermissionManager mPermissionManager;
    private DatabaseReference mMessageRef;
    private DatabaseReference mSyncedMessageRef;

    private Blessing mSourceBlessing;
    private Blessing mPublicBlessing;
    private final Set<String> targetDevices = new HashSet<>();

    private String mGroup;
    private String mAttachment;
    private int mAttachmentPermission;
    private RelativeLayout mAttachmentView;
    private TextView mAttachmentText;
    private ImageView mAttachmentIcon;
    private ImageView mAttachmentCast;

    private PermissionedTextLayout mTo;
    private PermissionedTextLayout mFrom;
    private PermissionedTextLayout mSubject;
    private PermissionedTextLayout mMessage;

    private Multimap<String, PermissionRequest> mRequests = HashMultimap.create();
    private HashMap<String, PermissionedTextLayout> mPermissionedFields = new HashMap<>();
    private HashMap<String, Integer> mPermissions = new HashMap<>();
    private HashMap<String, SyncText> syncTexts = new HashMap<>();

    private ArrayAdapter<String> contactAdapter;
    FloatingActionButton mFab;
    private Menu mMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_compose);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.setTitle("Compose");
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
        toolbar.setTitle("Compose");

        mFab = (FloatingActionButton) findViewById(R.id.fab);

        mTo = (PermissionedTextLayout) findViewById(R.id.composeTo);

        mFrom = (PermissionedTextLayout) findViewById(R.id.composeFrom);

        mSubject = (PermissionedTextLayout) findViewById(R.id.composeSubject);

        mMessage = (PermissionedTextLayout) findViewById(R.id.composeMessage);
        mMessage.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);

        mAttachmentView = (RelativeLayout) findViewById(R.id.composeAttachment);
        mAttachmentText = (TextView) findViewById(R.id.composeAttachmentText);
        mAttachmentIcon = (ImageView) findViewById(R.id.composeAttachmentIcon);
        mAttachmentIcon.setImageDrawable(new IconDrawable(this, MaterialIcons.md_attach_file).actionBarSize());
        mAttachmentCast = (ImageView) findViewById(R.id.composeAttachmentCast);
        mAttachmentCast.setImageDrawable(new IconDrawable(this, MaterialIcons.md_cast).actionBarSize());
        mAttachmentCast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mPublicBlessing != null) {
                    int current = mPublicBlessing.getPermissions(mPath + "/attachment");
                    if ((current & PermissionManager.FLAG_READ) == PermissionManager.FLAG_READ) {
                        mPublicBlessing.setPermissions(mPath + "/attachment",
                                current & ~PermissionManager.FLAG_READ);
                        mAttachmentCast.setImageDrawable(
                                new IconDrawable(ComposeActivity.this, MaterialIcons.md_cast).actionBarSize());
                    } else {
                        mPublicBlessing.setPermissions(mPath + "/attachment",
                                current | PermissionManager.FLAG_READ);
                        mAttachmentCast
                                .setImageDrawable(new IconDrawable(ComposeActivity.this, MaterialIcons.md_cancel)
                                        .color(Color.RED).actionBarSize());
                    }
                }
            }
        });
        mAttachmentCast.setVisibility(View.GONE);

        setGroup("Inbox"); //default
        setAttachment(null, mAttachmentPermission);

        getContactsPermission();

        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);
        menu.findItem(R.id.action_cast)
                .setIcon(new IconDrawable(this, MaterialIcons.md_cast).color(Color.WHITE).actionBarSize());
        MenuItem deleteItem = menu.findItem(R.id.action_delete);

        MenuItem attachItem = menu.findItem(R.id.action_attach)
                .setIcon(new IconDrawable(this, MaterialIcons.md_attach_file).color(Color.WHITE).actionBarSize());
        attachItem.setVisible(false);
        if (mOwner == null || !mOwner.equals(mDeviceId)) {
            attachItem.setVisible(false);
            deleteItem.setVisible(false);
        }
        mMenu = 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();

        if (id == R.id.action_delete) {
            checkAndSendMessage(); //currently send and delete have the same result
        } 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_attach) {
            pickAttachment();
        } else if (id == android.R.id.home) {
            finish();
        }

        return super.onOptionsItemSelected(item);
    }

    public void getContactsPermission() {
        // Here, thisActivity is the current activity
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {

            } else {
                ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.READ_CONTACTS }, 1);
            }
        } else { //already granted
            onContactsPermissionGranted();
        }
    }

    private void setGroup(String group) {
        mGroup = group;
        boolean editable = false;
        if ("Inbox".equals(group)) {
            mFab.setImageDrawable(new IconDrawable(this, MaterialIcons.md_reply).color(Color.WHITE));
            mFab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    launchAndCreateMessage(ComposeActivity.this, mMessageRef.getParent(), mFrom.getText(),
                            "myself@email.com", "RE: " + mSubject.getText(), "", null, "Drafts");
                }
            });

        } else if ("Drafts".equals(group)) {
            editable = true;
            mFab.setImageDrawable(new IconDrawable(this, MaterialIcons.md_send).color(Color.WHITE));
            if (mOwner == null || !mOwner.equals(mDeviceId)) {
                if (mMenu != null) {
                    mMenu.findItem(R.id.action_delete).setVisible(false);
                }
                mFab.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(ComposeActivity.this, "Can't send message from this device", 0).show();
                    }
                });
            } else {
                mFab.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        checkAndSendMessage();
                    }
                });
            }

            if (mMenu != null) {
                mMenu.findItem(R.id.action_attach).setVisible(true);
            }
        } else if ("Sent".equals(group)) {
            mFab.setImageDrawable(new IconDrawable(this, MaterialIcons.md_forward).color(Color.WHITE));
            mFab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    launchAndCreateMessage(ComposeActivity.this, mMessageRef.getParent(), "", "myself@email.com",
                            "FWD: " + mSubject.getText(), mMessage.getText(), null, "Drafts");
                }
            });
            if (mMenu != null) {
                mMenu.findItem(R.id.action_attach).setVisible(true);
            }
        }

        mTo.setEditable(editable);
        mSubject.setEditable(editable);
        mMessage.setEditable(editable);
    }

    public void setAttachment(final String attachment, final int permission) {
        this.mAttachment = attachment;
        this.mAttachmentPermission = permission;
        if (attachment == null || (permission & PermissionManager.FLAG_READ) != PermissionManager.FLAG_READ) {
            mAttachmentView.setVisibility(View.GONE);
        } else {
            mAttachmentView.setVisibility(View.VISIBLE);
            mAttachmentView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Utils.viewAttachment(ComposeActivity.this, attachment);
                }
            });
            mAttachmentText.setText(attachment);

        }
    }

    public static void launchAndCreateMessage(Context context, DatabaseReference messagesRef, String to,
            String from, String subject, String msg, String attachment, String group) {
        MessageData data = new MessageData(UUID.randomUUID().toString(), to, from, subject, msg, attachment, group);
        messagesRef.child(data.getId()).setValue(data);
        Intent intent = new Intent(context, ComposeActivity.class);
        intent.putExtra(ComposeActivity.EXTRA_MESSAGE_ID, data.getId());
        context.startActivity(intent);
    }

    private void onContactsPermissionGranted() {
        ArrayList<String> emailAddressCollection = new ArrayList<String>();
        ContentResolver cr = getContentResolver();
        Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, null, null, null);
        while (emailCur.moveToNext()) {
            String email = emailCur.getString(emailCur.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
            emailAddressCollection.add(email);
        }
        emailCur.close();
        String[] emailAddresses = new String[emailAddressCollection.size()];
        emailAddressCollection.toArray(emailAddresses);
        contactAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line,
                emailAddresses);
        mTo.setAutoCompleteAdapter(contactAdapter);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //TODO: generalize to other permissions. Not just contacts
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            onContactsPermissionGranted();
        }
    }

    ArrayList<SyncTextDiff> getSuggestions() {
        ArrayList<SyncTextDiff> result = new ArrayList<>();
        result.addAll(mTo.getSuggestions());
        result.addAll(mFrom.getSuggestions());
        result.addAll(mSubject.getSuggestions());
        result.addAll(mMessage.getSuggestions());
        return result;
    }

    void checkAndSendMessage() {
        //TODO: PermissionManager.requestDialog()
        mPermissionManager.request(mPath + "/send", mDeviceId).putExtra(PermissionManager.EXTRA_TIMEOUT, "2000")
                .putExtra(PermissionManager.EXTRA_COLOR, "#F00");

        if (getSuggestions().size() > 0) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Confirm suggestions before sending message");
            builder.setPositiveButton("Confirm", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    sendMessage();
                }
            });
            builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int id) {
                    // User cancelled the dialog
                }
            });

            AlertDialog dialog = builder.create();
            dialog.show();
        } else {
            sendMessage();
        }
    }

    public void sendMessage() {
        mMessageRef.child("group").setValue("Sent");
        for (String device : targetDevices) {
            mPermissionService.removeFromConstellation(device);
        }
        finish();
    }

    private void deleteMessage() {

        if ((mPermissionManager.getPermissions(mPath)
                & PermissionManager.FLAG_WRITE) == PermissionManager.FLAG_WRITE) {
            mMessageRef.removeValue();
        }
        finish();

    }

    private void pickAttachment() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        //        Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath());
        intent.setType("*/*");
        //        intent.setDataAndType(uri, "file/*");
        startActivityForResult(Intent.createChooser(intent, "Choose Attachment"), SELECT_ATTACHMENT);
    }

    @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 targetDevice = data.getStringExtra(DevicePickerActivity.EXTRA_DEVICE_ID);

            if (!mOwner.equals(targetDevice)) {
                //find most appropriate blessing to extend from
                mSourceBlessing = mPermissionManager.getBlessing(mOwner, mDeviceId);
                if (mSourceBlessing == null) {
                    mSourceBlessing = mPermissionManager.getRootBlessing();
                }
                //set default blessings
                //                Blessing deviceBlessing = mSourceBlessing.bless(targetDevice)
                //                        .setPermissions(mPath + "/to", PermissionManager.FLAG_SUGGEST)
                //                        .setPermissions(mPath + "/subject", PermissionManager.FLAG_SUGGEST)
                //                        .setPermissions(mPath + "/message", PermissionManager.FLAG_SUGGEST);
                addTargetDevice(targetDevice);
            }

            JSONObject castArgs = new JSONObject();
            try {
                castArgs.put("activity", ComposeActivity.class.getSimpleName());
                castArgs.put(EXTRA_MESSAGE_PATH, mPath);
                mPermissionService.getMessenger().to(targetDevice).emit("cast", castArgs.toString());
                mPermissionService.updateConstellationDevice(targetDevice);

            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else if (requestCode == SELECT_ATTACHMENT && resultCode == RESULT_OK) {
            Uri attachmentUri = data.getData();
            //            String path = Utils.getRealPathFromURI(this, attachmentUri);
            String path = Utils.getFileName(this, attachmentUri);
            //            File f = new File(path);
            Toast.makeText(this, "Attached " + path, 0).show();
            mMessageRef.child("attachment").setValue(path);
        }
    }

    public void addTargetDevice(String id) {
        targetDevices.add(id);
        for (Map.Entry<String, PermissionedTextLayout> entry : mPermissionedFields.entrySet()) {
            PermissionedTextLayout edit = entry.getValue();
            String key = entry.getKey();
            int current = mPublicBlessing.getPermissions(mPath + "/" + key);
            if ((current & PermissionManager.FLAG_SUGGEST) == PermissionManager.FLAG_SUGGEST) {
                edit.setAction(1, new IconDrawable(ComposeActivity.this, MaterialIcons.md_cancel).color(Color.RED)
                        .actionBarSize(), "Toggle Permission");
            } else {
                edit.setAction(1, new IconDrawable(ComposeActivity.this, MaterialIcons.md_cast).color(Color.BLACK)
                        .actionBarSize(), "Toggle Permission");
            }
        }
        mAttachmentCast.setVisibility(View.VISIBLE);
    }

    public void removeTargetDevice(String id) {
        targetDevices.remove(id);
        if (targetDevices.size() == 0) {
            for (Map.Entry<String, PermissionedTextLayout> entry : mPermissionedFields.entrySet()) {
                PermissionedTextLayout edit = entry.getValue();
                edit.clearActions();
            }
            mAttachmentCast.setVisibility(View.GONE);
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        PermissionService.PermissionServiceBinder binder = (PermissionService.PermissionServiceBinder) service;
        mPermissionService = binder.getInstance();

        if (mPermissionService != null) {
            mPermissionManager = mPermissionService.getPermissionManager();
            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;
            }

            //parse path to get owner
            String[] pathElements = mPath.split("/");
            if (pathElements != null && pathElements.length > 1) {
                mOwner = pathElements[1];
            }

            if (mMenu != null && !mOwner.equals(mDeviceId)) {
                mMenu.findItem(R.id.action_attach).setVisible(false);
                mMenu.findItem(R.id.action_delete).setVisible(false);
            }

            mMessageRef = mPermissionService.getFirebaseDB().getReference(mPath);
            mSyncedMessageRef = mPermissionService.getFirebaseDB()
                    .getReference("documents/" + mOwner + "/emails/syncedMessages/" + mId);
            mPermissionManager.addPermissionEventListener(mPath, messagePermissionListener);

            mMessageRef.child("group").addValueEventListener(groupListener);

            mMessageRef.child("attachment").addValueEventListener(attachmentListener);
            mPermissionManager.addPermissionEventListener(mPath + "/attachment", attachmentPermissionListner);

            initField(mTo, "to");
            initField(mFrom, "from");
            initField(mSubject, "subject");
            initField(mMessage, "message");

            mPublicBlessing = mPermissionManager.bless("public");
            //                    .setPermissions(mPath + "/subject", PermissionManager.FLAG_READ);

            mPermissionManager.addOnRequestListener("documents/" + mDeviceId + "/emails/messages/" + mId + "/*",
                    new PermissionManager.OnRequestListener() {
                        @Override
                        public boolean onRequest(PermissionRequest request, Blessing blessing) {
                            mRequests.put(request.getPath(), request);
                            return true;
                        }

                        @Override
                        public void onRequestRemoved(PermissionRequest request, Blessing blessing) {

                        }
                    });
            mPermissionService.setStatus(EXTRA_MESSAGE_PATH, mPath);

            mPermissionService.addDiscoveryListener(new PermissionService.DiscoveryListener() {
                @Override
                public void onChange(Map<String, DeviceData> devices) {

                }

                @Override
                public void onDisassociate(String deviceId) {
                    if (deviceId.equals(mOwner)) {
                        finish();
                    } else {
                        removeTargetDevice(deviceId);
                    }
                }
            });
        }
    }

    PermissionManager.OnPermissionChangeListener attachmentPermissionListner = new PermissionManager.OnPermissionChangeListener() {
        @Override
        public void onPermissionChange(int current) {
            setAttachment(mAttachment, current);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    ValueEventListener groupListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            if (dataSnapshot.exists()) {
                String newGroup = dataSnapshot.getValue(String.class);
                setGroup(newGroup);
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    ValueEventListener attachmentListener = new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            if (dataSnapshot.exists()) {
                String newAttachment = dataSnapshot.getValue(String.class);
                setAttachment(newAttachment, mAttachmentPermission);
            }
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    };

    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 initField(final PermissionedTextLayout edit, final String key) {
        edit.setSyncText(new SyncText(mDeviceId, PermissionManager.FLAG_SUGGEST, mSyncedMessageRef.child(key),
                mMessageRef.child(key)));
        edit.setPermissionedTextListener(new PermissionedTextLayout.PermissionedTextListener() {
            @Override
            public void onSelected(final SyncTextDiff diff, PermissionedTextLayout text) {
                int current = mPermissionManager.getPermissions(mPath + "/" + key);
                if ((current & PermissionManager.FLAG_WRITE) == PermissionManager.FLAG_WRITE) {
                    Intent content = new Intent(ComposeActivity.this, ComposeActivity.class);
                    content.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
                    String name = diff.getSource();
                    if (mPermissionService.getDiscovered().containsKey(name)) {
                        name = mPermissionService.getDiscovered().get(diff.source).getName();
                    }
                    mPermissionService.requestDialog(diff.source, "Apply changes from " + name, "be vigilant",
                            new PermissionService.ActionCallback() {
                                @Override
                                public boolean onAction(Intent intent) {
                                    acceptSuggestions(diff.source);
                                    return true;
                                }
                            }, new PermissionService.ActionCallback() {
                                @Override
                                public boolean onAction(Intent intent) {
                                    rejectSuggestions(diff.source);
                                    return true;
                                }
                            }, content);
                }
            }

            @Override
            public void onAction(int action, PermissionedTextLayout text) {
                if (action == 1) {
                    if (mPublicBlessing != null) {
                        int current = mPublicBlessing.getPermissions(mPath + "/" + key);
                        if ((current & PermissionManager.FLAG_SUGGEST) == PermissionManager.FLAG_SUGGEST) {
                            mPublicBlessing.setPermissions(mPath + "/" + key,
                                    current & ~PermissionManager.FLAG_SUGGEST);
                            text.setAction(1, new IconDrawable(ComposeActivity.this, MaterialIcons.md_cast)
                                    .color(Color.BLACK).actionBarSize(), "Toggle Permission");
                        } else {
                            mPublicBlessing.setPermissions(mPath + "/" + key,
                                    current | PermissionManager.FLAG_SUGGEST);
                            text.setAction(1, new IconDrawable(ComposeActivity.this, MaterialIcons.md_cancel)
                                    .color(Color.RED).actionBarSize(), "Toggle Permission");
                        }
                    }
                } else if (action == 2) {
                    mPermissionManager.request(mPath + "/to", mDeviceId)
                            .setPermissions(PermissionManager.FLAG_WRITE).udpate();
                }
            }
        });

        mPermissionedFields.put(key, edit);
        final String path = "documents/" + mDeviceId + "/emails/messages/" + mId + "/" + key;

        mPermissionManager.addPermissionEventListener(mPath + "/" + key,
                new PermissionManager.OnPermissionChangeListener() {
                    @Override
                    public void onPermissionChange(int current) {
                        edit.onPermissionChange(current);

                        //TODO:generalize the following request button
                        if ("to".equals(key)) {
                            if (current == PermissionManager.FLAG_READ) {
                                edit.setAction(2, new IconDrawable(ComposeActivity.this, MaterialIcons.md_vpn_key),
                                        "Request Permission");
                            } else {
                                edit.removeAction(2);
                            }

                            if ((current & PermissionManager.FLAG_WRITE) == PermissionManager.FLAG_WRITE) {
                                edit.setAutoCompleteAdapter(contactAdapter);
                            } else {
                                edit.setAutoCompleteAdapter(null);
                            }
                        }
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {

                    }
                });
    }

    public void acceptSuggestions(String src) {
        for (PermissionedTextLayout text : mPermissionedFields.values()) {
            text.acceptSuggestions(src);
        }
    }

    public void rejectSuggestions(String src) {
        for (PermissionedTextLayout text : mPermissionedFields.values()) {
            text.rejectSuggestions(src);
        }
    }

    public void unlink() {
        for (PermissionedTextLayout text : mPermissionedFields.values()) {
            text.unlink();
        }
        mMessageRef.child("attachment").removeEventListener(attachmentListener);
        mMessageRef.child("group").removeEventListener(groupListener);
        if (mPermissionService != null) {
            if (mPublicBlessing != null) {
                mPublicBlessing.revokePermissions(mPath);
            }

            //cancel all requests made from this activity
            mPermissionManager.cancelRequests(mDeviceId + mId);
        }

        unbindService(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unlink();
    }
}