From 5ae33f9648e54289b8d8d0b91c2fd298904db418 Mon Sep 17 00:00:00 2001 From: Joachim Fritzsch Date: Wed, 17 Apr 2013 15:55:14 +0200 Subject: [PATCH] -android: Optimization of EventLogActivity and message retrieval --- .../res/layout/eventlog_layout_listitem.xml | 62 ++--- .../src/edu/berkeley/boinc/BOINCActivity.java | 6 +- .../edu/berkeley/boinc/EventLogActivity.java | 234 +++++++++++++----- .../src/edu/berkeley/boinc/PrefsActivity.java | 2 - .../boinc/adapter/EventLogListAdapter.java | 7 +- .../berkeley/boinc/client/ClientStatus.java | 22 +- .../edu/berkeley/boinc/client/Monitor.java | 45 +++- 7 files changed, 245 insertions(+), 133 deletions(-) diff --git a/android/BOINC/res/layout/eventlog_layout_listitem.xml b/android/BOINC/res/layout/eventlog_layout_listitem.xml index 7712a679a5..97d616a468 100644 --- a/android/BOINC/res/layout/eventlog_layout_listitem.xml +++ b/android/BOINC/res/layout/eventlog_layout_listitem.xml @@ -17,51 +17,39 @@ You should have received a copy of the GNU Lesser General Public License along with BOINC. If not, see . --> - + android:layout_height="match_parent" > + android:focusable="false" + android:layout_margin="5dp" /> - + + + - - - - - - - - - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/android/BOINC/src/edu/berkeley/boinc/BOINCActivity.java b/android/BOINC/src/edu/berkeley/boinc/BOINCActivity.java index 4a6afc6fe5..cd3902f347 100644 --- a/android/BOINC/src/edu/berkeley/boinc/BOINCActivity.java +++ b/android/BOINC/src/edu/berkeley/boinc/BOINCActivity.java @@ -72,7 +72,7 @@ public class BOINCActivity extends TabActivity { private BroadcastReceiver mClientStatusChangeRec = new BroadcastReceiver() { @Override public void onReceive(Context context,Intent intent) { - Log.d(TAG, "ClientStatusChange - onReceive()"); + //Log.d(TAG, "ClientStatusChange - onReceive()"); determineStatus(); } @@ -142,10 +142,10 @@ public class BOINCActivity extends TabActivity { try { if(mIsBound) { newStatus = Monitor.getClientStatus().setupStatus; - Log.d(TAG,"determineStatus() old clientSetupStatus: " + clientSetupStatus + " - newStatus: " + newStatus); if(newStatus != clientSetupStatus) { //only act, when status actually different form old status + Log.d(TAG,"determineStatus() client setup status changed! old clientSetupStatus: " + clientSetupStatus + " - new: " + newStatus); clientSetupStatus = newStatus; - layout(); + layout(); } if(intialStart && (clientSetupStatus == ClientStatus.SETUP_STATUS_NOPROJECT)) { // if it is first start and no project attached, show login activity startActivity(new Intent(this,AttachProjectListActivity.class)); diff --git a/android/BOINC/src/edu/berkeley/boinc/EventLogActivity.java b/android/BOINC/src/edu/berkeley/boinc/EventLogActivity.java index fa61eedb25..3a76060ca1 100644 --- a/android/BOINC/src/edu/berkeley/boinc/EventLogActivity.java +++ b/android/BOINC/src/edu/berkeley/boinc/EventLogActivity.java @@ -19,20 +19,24 @@ package edu.berkeley.boinc; import java.util.ArrayList; +import java.util.List; import java.lang.StringBuffer; import edu.berkeley.boinc.adapter.EventLogListAdapter; import edu.berkeley.boinc.client.Monitor; import edu.berkeley.boinc.rpc.Message; -import android.content.BroadcastReceiver; -import android.content.Context; +import android.content.ComponentName; import android.content.Intent; -import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.AsyncTask; import android.os.Bundle; +import android.os.IBinder; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.MenuInflater; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; import android.widget.TextView; @@ -40,47 +44,26 @@ import android.widget.TextView; public class EventLogActivity extends FragmentActivity { private final String TAG = "BOINC EventLogActivity"; + + private Monitor monitor; + private Boolean mIsBound = false; private ListView lv; private EventLogListAdapter listAdapter; private ArrayList data = new ArrayList(); - private int lastSeqno = 0; - - // Controls whether initialization of view elements of "projects_layout" - // is required. This is the case, every time the layout switched. - private Boolean initialSetupRequired = true; - // BroadcastReceiver event is used to update the UI with updated information from - // the client. This is generally called once a second. - // - private IntentFilter ifcsc = new IntentFilter("edu.berkeley.boinc.clientstatuschange"); - private BroadcastReceiver mClientStatusChangeRec = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Log.d(TAG, "ClientStatusChange - onReceive()"); - - populateLayout(); - } - }; - + // message retrieval + private Integer pastMsgsLoadingRange = 50; // amount messages loaded when end of list is reached - // - // Message Activity - // @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate()"); + doBindService(); + setLayoutLoading(); + super.onCreate(savedInstanceState); } - - @Override - public void onPause() { - Log.d(TAG, "onPause()"); - - unregisterReceiver(mClientStatusChangeRec); - super.onPause(); - } @Override public void onResume() { @@ -88,47 +71,87 @@ public class EventLogActivity extends FragmentActivity { super.onResume(); - populateLayout(); - - registerReceiver(mClientStatusChangeRec, ifcsc); + new RetrieveRecentMsgs().execute(); // refresh messages } @Override protected void onDestroy() { Log.d(TAG, "onDestroy()"); - + doUnbindService(); super.onDestroy(); } - private void populateLayout() { + /* + * Service binding part + * only necessary, when function on monitor instance has to be called + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG,"onServiceConnected"); + monitor = ((Monitor.LocalBinder)service).getService(); + mIsBound = true; + initializeLayout(); + } + + public void onServiceDisconnected(ComponentName className) { + monitor = null; + mIsBound = false; + } + }; + + private void doBindService() { + if(!mIsBound) { + getApplicationContext().bindService(new Intent(this, Monitor.class), mConnection, 0); //calling within Tab needs getApplicationContext() for bindService to work! + } + } + + private void doUnbindService() { + if (mIsBound) { + getApplicationContext().unbindService(mConnection); + mIsBound = false; + } + } + + // updates data list with most recent messages + private void loadRecentMsgs(ArrayList tmpA) { + // Prepend new messages to the event log try { - // Read messages from state saved in ClientStatus - ArrayList tmpA = Monitor.getClientStatus().getMessages(); - - if(tmpA == null) { + int y = 0; + for (int x = tmpA.size()-1; x >= 0; x--) { + data.add(y, tmpA.get(x)); + y++; + } + } catch (Exception e) {} //IndexOutOfBoundException + listAdapter.notifyDataSetChanged(); + } + + // appends older messages to data list + private void loadPastMsgs(List tmpA) { + // Append old messages to the event log + try { + for(int x = tmpA.size()-1; x >= 0; x--) { + data.add(tmpA.get(x)); + } + } catch (Exception e) {} //IndexOutOfBoundException + + listAdapter.notifyDataSetChanged(); + } + + private void initializeLayout() { + try { + // check whether monitor is bound + if(!mIsBound) { setLayoutLoading(); return; } - - // Switch to a view that can actually display messages - if (initialSetupRequired) { - initialSetupRequired = false; - setContentView(R.layout.eventlog_layout); - lv = (ListView) findViewById(R.id.eventlogList); - listAdapter = new EventLogListAdapter(EventLogActivity.this, lv, R.id.eventlogList, data); - } - - // Add new messages to the event log - for (Message msg: tmpA) { - if (msg.seqno > lastSeqno) { - data.add(msg); - lastSeqno = msg.seqno; - } - } - - // Force list adapter to refresh - listAdapter.notifyDataSetChanged(); + + setContentView(R.layout.eventlog_layout); + lv = (ListView) findViewById(R.id.eventlogList); + listAdapter = new EventLogListAdapter(EventLogActivity.this, lv, R.id.eventlogList, data); + lv.setOnScrollListener(new EndlessScrollListener(5)); + // initial data retrieval + new RetrievePastMsgs().execute(); } catch (Exception e) { // data retrieval failed, set layout to loading... setLayoutLoading(); @@ -139,7 +162,6 @@ public class EventLogActivity extends FragmentActivity { setContentView(R.layout.generic_layout_loading); TextView loadingHeader = (TextView)findViewById(R.id.loading_header); loadingHeader.setText(R.string.eventlog_loading); - initialSetupRequired = true; } @Override @@ -217,4 +239,94 @@ public class EventLogActivity extends FragmentActivity { startActivity(Intent.createChooser(emailIntent, "Send mail...")); } + + // onScrollListener for list view, implementing "endless scrolling" + public final class EndlessScrollListener implements OnScrollListener { + + private int visibleThreshold = 5; + private int previousTotal = 0; + private boolean loading = true; + + public EndlessScrollListener(int visibleThreshold) { + this.visibleThreshold = visibleThreshold; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + if (loading) { + if (totalItemCount > previousTotal) { + loading = false; + previousTotal = totalItemCount; + } + } + if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) { + new RetrievePastMsgs().execute(); + loading = true; + } + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + } + } + + private final class RetrieveRecentMsgs extends AsyncTask> { + + private Integer mostRecentSeqNo = 0; + + @Override + protected void onPreExecute() { + if(!mIsBound) cancel(true); // cancel execution if monitor is not bound yet + try { + mostRecentSeqNo = data.get(0).seqno; + } catch (Exception e) {} //IndexOutOfBoundException + } + + @Override + protected ArrayList doInBackground(Void... params) { + return monitor.getEventLogMessages(mostRecentSeqNo); + } + + @Override + protected void onPostExecute(ArrayList result) { + // back in UI thread + loadRecentMsgs(result); + } + } + + private final class RetrievePastMsgs extends AsyncTask> { + + private Integer mostRecentSeqNo = null; + private Integer pastSeqNo = null; + + @Override + protected void onPreExecute() { + if(!mIsBound) cancel(true); // cancel execution if monitor is not bound yet + try { + mostRecentSeqNo = data.get(0).seqno; + pastSeqNo = data.get(data.size()-1).seqno; + Log.d("RetrievePastMsgs","mostRecentSeqNo: " + mostRecentSeqNo + " ; pastSeqNo: " + pastSeqNo); + if(pastSeqNo==0) { + Log.d("RetrievePastMsgs", "cancel, all past messages are present"); + cancel(true); // cancel if all past messages are present + } + } catch (Exception e) {} //IndexOutOfBoundException + } + + @Override + protected List doInBackground(Void... params) { + Integer startIndex = 0; + if(mostRecentSeqNo != null && pastSeqNo != null && mostRecentSeqNo != 0 && pastSeqNo != 0) startIndex = mostRecentSeqNo - pastSeqNo + 1; + Integer lastIndexOfList = 0; + if(mostRecentSeqNo != null) lastIndexOfList = mostRecentSeqNo - 1; + //Log.d("RetrievePastMsgs", "calling monitor with: " + startIndex + lastIndexOfList); + return monitor.getEventLogMessages(startIndex, pastMsgsLoadingRange, lastIndexOfList); + } + + @Override + protected void onPostExecute(List result) { + // back in UI thread + loadPastMsgs(result); + } + } } diff --git a/android/BOINC/src/edu/berkeley/boinc/PrefsActivity.java b/android/BOINC/src/edu/berkeley/boinc/PrefsActivity.java index 7e9fcd7786..81418c2cd7 100644 --- a/android/BOINC/src/edu/berkeley/boinc/PrefsActivity.java +++ b/android/BOINC/src/edu/berkeley/boinc/PrefsActivity.java @@ -71,8 +71,6 @@ public class PrefsActivity extends FragmentActivity { /* * Service binding part * only necessary, when function on monitor instance has to be called - * currently in Prefs- and DebugActivity - * */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { diff --git a/android/BOINC/src/edu/berkeley/boinc/adapter/EventLogListAdapter.java b/android/BOINC/src/edu/berkeley/boinc/adapter/EventLogListAdapter.java index 1993dc626c..614ef30e3e 100644 --- a/android/BOINC/src/edu/berkeley/boinc/adapter/EventLogListAdapter.java +++ b/android/BOINC/src/edu/berkeley/boinc/adapter/EventLogListAdapter.java @@ -117,7 +117,12 @@ public class EventLogListAdapter extends ArrayAdapter implements OnItem viewEventLog.cbCheck.setChecked(listView.isItemChecked(position)); viewEventLog.tvMessage.setText(getMessage(position)); viewEventLog.tvDate.setText(getDate(position)); - viewEventLog.tvProjectName.setText(getProject(position)); + if(getProject(position).isEmpty()){ + viewEventLog.tvProjectName.setVisibility(View.GONE); + } else { + viewEventLog.tvProjectName.setVisibility(View.VISIBLE); + viewEventLog.tvProjectName.setText(getProject(position)); + } return vi; } diff --git a/android/BOINC/src/edu/berkeley/boinc/client/ClientStatus.java b/android/BOINC/src/edu/berkeley/boinc/client/ClientStatus.java index abd90d8bf9..39f072f181 100644 --- a/android/BOINC/src/edu/berkeley/boinc/client/ClientStatus.java +++ b/android/BOINC/src/edu/berkeley/boinc/client/ClientStatus.java @@ -27,7 +27,6 @@ import android.os.PowerManager.WakeLock; import android.util.Log; import edu.berkeley.boinc.rpc.CcStatus; import edu.berkeley.boinc.rpc.GlobalPreferences; -import edu.berkeley.boinc.rpc.Message; import edu.berkeley.boinc.rpc.Project; import edu.berkeley.boinc.rpc.Result; import edu.berkeley.boinc.rpc.Transfer; @@ -51,7 +50,6 @@ public class ClientStatus { private ArrayList projects; private ArrayList transfers; private GlobalPreferences prefs; - private ArrayList messages; //debug tab // setup status public Integer setupStatus = 0; @@ -130,12 +128,11 @@ public class ClientStatus { /* * called frequently by Monitor to set the RPC data. These objects are used to determine the client status and parse it in the data model of this class. */ - public synchronized void setClientStatus(CcStatus status,ArrayList results,ArrayList projects, ArrayList transfers, ArrayList msgs) { + public synchronized void setClientStatus(CcStatus status,ArrayList results,ArrayList projects, ArrayList transfers) { this.status = status; this.results = results; this.projects = projects; this.transfers = transfers; - this.messages = msgs; parseClientStatus(); Log.d(TAG,"parsing results: computing: " + computingParseError + computingStatus + computingSuspendReason + " - network: " + networkParseError + networkStatus + networkSuspendReason); if(!computingParseError && !networkParseError && !setupStatusParseError) { @@ -203,23 +200,6 @@ public class ClientStatus { return projects; } - //Debug Tab - public synchronized ArrayList getMessages() { - if(messages == null) { //check in case monitor is not set up yet (e.g. while logging in) - Log.d(TAG, "messages is null"); - return null; - } - return messages; - } - - public synchronized void resetMessages() { - if(messages == null) { //check in case monitor is not set up yet (e.g. while logging in) - Log.d(TAG, "messages is null"); - return; - } - messages.clear(); - } - /* * parses RPC data to ClientStatus data model. */ diff --git a/android/BOINC/src/edu/berkeley/boinc/client/Monitor.java b/android/BOINC/src/edu/berkeley/boinc/client/Monitor.java index 4775ad83bd..e822d32363 100644 --- a/android/BOINC/src/edu/berkeley/boinc/client/Monitor.java +++ b/android/BOINC/src/edu/berkeley/boinc/client/Monitor.java @@ -32,6 +32,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; + import android.app.NotificationManager; import android.app.Service; import android.content.Intent; @@ -851,6 +853,40 @@ public class Monitor extends Service { return auth; } + // returns client messages to be shown in EventLog tab. + // start is counting from beginning, e.g. start=0 number=50 + // needs lastIndexOfList as reference for consistency + // returns 50 most recent messages + public List getEventLogMessages(int startIndex, int number, int lastIndexOfList) { + //Log.d(TAG,"getEventLogMessage from start: " + startIndex + " amount: " + number + "lastIndexOfList: " + lastIndexOfList); + Integer requestedLastIndex = startIndex + number; + if(lastIndexOfList == 0) { // list is empty, initial start + lastIndexOfList = rpc.getMessageCount() - 1; // possible lastIndexOfList if all messages were read + //Log.d(TAG,"list ist empty, initial start, read message count: " + (lastIndexOfList+1)); + } + if(requestedLastIndex > lastIndexOfList + 1) { // requesting more messages than actually present + number = lastIndexOfList - startIndex + 1; + //Log.d(TAG,"getEventLogMessage requesting more messages than left, number changed to " + number); + } + Integer param = lastIndexOfList + 1 - startIndex - number; + //Log.d(TAG,"getEventLogMessage calling RPC with: " + param); + try { + ArrayList tmpL = rpc.getMessages(param); + List msgs = tmpL.subList(0, tmpL.size() - startIndex); // tmp.size - start is amount of actually new values. Usually equals number, except for end of list + //Log.d(TAG,"getEventLogMessages returning " + msgs.size() + " messages with oldest element seq no: " + msgs.get(0).seqno + " and recent seq no: " + msgs.get(msgs.size()-1).seqno); + return msgs; + } catch (Exception e) { + //Log.w(TAG,"error in retrieving sublist", e); + return null; + } + } + + // returns client messages that are more recent than given seqNo + public ArrayList getEventLogMessages(int seqNo) { + //Log.d(TAG, "getEventLogMessage more recent than seqNo: " + seqNo); + return rpc.getMessages(seqNo); + } + private final class ClientMonitorAsync extends AsyncTask { private final String TAG = "BOINC ClientMonitorAsync"; @@ -884,16 +920,9 @@ public class Monitor extends Service { ArrayList projects = rpc.getProjectStatus(); if(showRpcCommands) Log.d(TAG, "getTransers"); ArrayList transfers = rpc.getFileTransfers(); - ArrayList msgs = new ArrayList(); - // retrieve messages only, if tabs are actually enabled. very resource intense with logging on emulator! - if(getResources().getBoolean(R.bool.tab_eventlog)) { - Integer count = rpc.getMessageCount(); - msgs = rpc.getMessages(count - 250); //get the most recent 250 messages - if(showRpcCommands) Log.d(TAG, "getMessages, count: " + count); - } if( (status != null) && (results != null) && (projects != null) && (transfers != null)) { - Monitor.getClientStatus().setClientStatus(status, results, projects, transfers, msgs); + Monitor.getClientStatus().setClientStatus(status, results, projects, transfers); } else { Log.d(TAG, "client status connection problem"); }