Newer
Older
package org.briarproject.android.groups;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.groups.ReadGroupPostActivity.RESULT_PREV_NEXT;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.briarproject.R;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.ListLoadingProgressBar;
import org.briarproject.api.Author;
import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.MessageHeader;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.NoSuchSubscriptionException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.event.MessageExpiredEvent;
import org.briarproject.api.event.SubscriptionRemovedEvent;
import org.briarproject.api.messaging.Group;
import org.briarproject.api.messaging.GroupId;
import org.briarproject.api.messaging.MessageId;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
public class GroupActivity extends BriarActivity implements EventListener,
OnClickListener, OnItemClickListener {
private static final int REQUEST_READ = 2;
private static final Logger LOG =
Logger.getLogger(GroupActivity.class.getName());
@Inject private AndroidNotificationManager notificationManager;
private Map<MessageId, byte[]> bodyCache = new HashMap<MessageId, byte[]>();
private TextView empty = null;
private GroupAdapter adapter = null;
private ListView list = null;
private ListLoadingProgressBar loading = null;
// Fields that are accessed from background threads must be volatile
@Inject private volatile DatabaseComponent db;
private volatile GroupId groupId = null;
private volatile String groupName = null;
@Override
public void onCreate(Bundle state) {
Intent i = getIntent();
byte[] b = i.getByteArrayExtra("briar.GROUP_ID");
if(b == null) throw new IllegalStateException();
groupId = new GroupId(b);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
empty = new TextView(this);
empty.setLayoutParams(MATCH_WRAP_1);
empty.setGravity(CENTER);
empty.setTextSize(18);
empty.setText(R.string.no_posts);
empty.setVisibility(GONE);
layout.addView(empty);
adapter = new GroupAdapter(this);
list = new ListView(this);
list.setLayoutParams(MATCH_WRAP_1);
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setVisibility(GONE);
layout.addView(list);
// Show a progress bar while the list is loading
loading = new ListLoadingProgressBar(this);
layout.addView(loading);
layout.addView(new HorizontalBorder(this));
LinearLayout footer = new LinearLayout(this);
footer.setLayoutParams(MATCH_WRAP);
footer.setGravity(CENTER);
Resources res = getResources();
footer.setBackgroundColor(res.getColor(R.color.button_bar_background));
ImageButton composeButton = new ImageButton(this);
composeButton.setBackgroundResource(0);
composeButton.setImageResource(R.drawable.content_new_email);
composeButton.setOnClickListener(this);
footer.addView(composeButton);
layout.addView(footer);
setContentView(layout);
}
@Override
public void onResume() {
super.onResume();
db.addListener(this);
loadHeaders();
}
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
private void loadGroup() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
Group g = db.getGroup(groupId);
groupName = g.getName();
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Loading group " + duration + " ms");
displayGroupName();
} catch(NoSuchSubscriptionException e) {
finishOnUiThread();
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayGroupName() {
runOnUiThread(new Runnable() {
public void run() {
setTitle(groupName);
}
});
}
private void loadHeaders() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
Collection<MessageHeader> headers =
db.getMessageHeaders(groupId);
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayHeaders(headers);
finishOnUiThread();
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayHeaders(final Collection<MessageHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
loading.setVisibility(GONE);
if(headers.isEmpty()) {
empty.setVisibility(VISIBLE);
list.setVisibility(GONE);
} else {
empty.setVisibility(GONE);
list.setVisibility(VISIBLE);
for(MessageHeader h : headers) {
GroupItem item = new GroupItem(h);
byte[] body = bodyCache.get(h.getId());
if(body == null) loadMessageBody(h);
else item.setBody(body);
adapter.add(item);
}
adapter.sort(GroupItemComparator.INSTANCE);
// Scroll to the bottom
list.setSelection(adapter.getCount() - 1);
adapter.notifyDataSetChanged();
}
});
}
private void loadMessageBody(final MessageHeader h) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
byte[] body = db.getMessageBody(h.getId());
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Loading message took " + duration + " ms");
displayMessage(h.getId(), body);
} catch(NoSuchMessageException e) {
// The item will be removed when we get the event
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayMessage(final MessageId m, final byte[] body) {
runOnUiThread(new Runnable() {
public void run() {
bodyCache.put(m, body);
int count = adapter.getCount();
for(int i = 0; i < count; i++) {
GroupItem item = adapter.getItem(i);
if(item.getHeader().getId().equals(m)) {
item.setBody(body);
adapter.notifyDataSetChanged();
// Scroll to the bottom
list.setSelection(count - 1);
return;
}
}
}
});
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if(request == REQUEST_READ && result == RESULT_PREV_NEXT) {
int position = data.getIntExtra("briar.POSITION", -1);
if(position >= 0 && position < adapter.getCount())
}
}
@Override
public void onPause() {
db.removeListener(this);
if(isFinishing()) markMessagesRead();
}
private void markMessagesRead() {
notificationManager.clearGroupPostNotification(groupId);
List<MessageId> unread = new ArrayList<MessageId>();
int count = adapter.getCount();
for(int i = 0; i < count; i++) {
MessageHeader h = adapter.getItem(i).getHeader();
if(!h.isRead()) unread.add(h.getId());
}
if(unread.isEmpty()) return;
if(LOG.isLoggable(INFO))
LOG.info("Marking " + unread.size() + " messages read");
markMessagesRead(Collections.unmodifiableList(unread));
}
private void markMessagesRead(final Collection<MessageId> unread) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
for(MessageId m : unread) db.setReadFlag(m, true);
long duration = System.currentTimeMillis() - now;
if(LOG.isLoggable(INFO))
LOG.info("Marking read took " + duration + " ms");
} catch(DbException e) {
if(LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
public void eventOccurred(Event e) {
if(e instanceof MessageAddedEvent) {
if(((MessageAddedEvent) e).getGroup().getId().equals(groupId)) {
LOG.info("Message added, reloading");
loadHeaders();
} else if(e instanceof MessageExpiredEvent) {
LOG.info("Message expired, reloading");
} else if(e instanceof SubscriptionRemovedEvent) {
SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
if(s.getGroup().getId().equals(groupId)) {
LOG.info("Subscription removed");
finishOnUiThread();
}
}
public void onClick(View view) {
Intent i = new Intent(this, WriteGroupPostActivity.class);
i.putExtra("briar.GROUP_ID", groupId.getBytes());
i.putExtra("briar.GROUP_NAME", groupName);
i.putExtra("briar.MIN_TIMESTAMP", getMinTimestampForNewMessage());
startActivity(i);
}
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
long timestamp = 0;
int count = adapter.getCount();
for(int i = 0; i < count; i++) {
long t = adapter.getItem(i).getHeader().getTimestamp();
if(t > timestamp) timestamp = t;
}
return timestamp + 1;
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
}
private void displayMessage(int position) {
MessageHeader item = adapter.getItem(position).getHeader();
Intent i = new Intent(this, ReadGroupPostActivity.class);
i.putExtra("briar.GROUP_ID", groupId.getBytes());
i.putExtra("briar.GROUP_NAME", groupName);
i.putExtra("briar.MESSAGE_ID", item.getId().getBytes());
Author author = item.getAuthor();
if(author != null) i.putExtra("briar.AUTHOR_NAME", author.getName());
i.putExtra("briar.AUTHOR_STATUS", item.getAuthorStatus().name());
i.putExtra("briar.CONTENT_TYPE", item.getContentType());
i.putExtra("briar.TIMESTAMP", item.getTimestamp());
i.putExtra("briar.MIN_TIMESTAMP", getMinTimestampForNewMessage());
i.putExtra("briar.POSITION", position);
startActivityForResult(i, REQUEST_READ);