com.darshancomputing.BatteryIndicatorPro.LogViewFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.darshancomputing.BatteryIndicatorPro.LogViewFragment.java

Source

/*
Copyright (c) 2010-2013 Darshan-Josiah Barber
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
*/

package com.darshancomputing.BatteryIndicatorPro;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import android.support.v4.app.DialogFragment;
import android.support.v4.app.ListFragment;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;

public class LogViewFragment extends ListFragment {
    private static BatteryInfoActivity activity;
    private LogDatabase logs;
    private Col col;
    private Cursor completeCursor;
    private Cursor filteredCursor;
    private Cursor timeDeltaCursor;
    private LayoutInflater mInflater;
    private LogAdapter mAdapter;
    private TextView header_text;
    private Boolean reversed = false;
    private Boolean convertF;

    private static final String LOG_TAG = "BatteryBot";

    private static final String[] CSV_ORDER = { LogDatabase.KEY_TIME, LogDatabase.KEY_STATUS_CODE,
            LogDatabase.KEY_CHARGE, LogDatabase.KEY_TEMPERATURE, LogDatabase.KEY_VOLTAGE };

    private final Messenger messenger = new Messenger(new MessageHandler());
    private boolean serviceConnected;
    private Messenger serviceMessenger;
    private BatteryInfoService.RemoteConnection serviceConnection;

    public void bindService() {
        if (!serviceConnected) {
            Intent biServiceIntent = new Intent(activity.context, BatteryInfoService.class);
            serviceConnection = new BatteryInfoService.RemoteConnection(messenger);

            activity.context.bindService(biServiceIntent, serviceConnection, 0);
            serviceConnected = true;
        }
    }

    public class MessageHandler extends Handler {
        @Override
        public void handleMessage(Message incoming) {
            if (!serviceConnected) {
                Log.i(LOG_TAG, "serviceConected is false; ignoring message: " + incoming);
                return;
            }

            switch (incoming.what) {
            case BatteryInfoService.RemoteConnection.CLIENT_SERVICE_CONNECTED:
                serviceMessenger = incoming.replyTo;
                sendServiceMessage(BatteryInfoService.RemoteConnection.SERVICE_REGISTER_CLIENT);
                break;
            case BatteryInfoService.RemoteConnection.CLIENT_BATTERY_INFO_UPDATED:
                reloadList(false);
                break;
            default:
                super.handleMessage(incoming);
            }
        }
    }

    private void sendServiceMessage(int what) {
        Message outgoing = Message.obtain();
        outgoing.what = what;
        outgoing.replyTo = messenger;
        try {
            serviceMessenger.send(outgoing);
        } catch (android.os.RemoteException e) {
        }
    }

    /*
    private static final IntentFilter batteryChangedFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    private final Handler mHandler = new Handler();
    private final Runnable mUpdateStatus = new Runnable() {
    public void run() {
        reloadList(false);
    }
    };
        
    private final BroadcastReceiver mBatteryInfoReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (! Intent.ACTION_BATTERY_CHANGED.equals(action)) return;
        
        // TODO: Do this better.  Perphaps have the Activity be the client of the Service, or perhaps
        //  leave it with CIF as client, but it calls Activity.infoUpdated() after receiving message
        //  from Service.  If so, make sure (and document) that Service shouldn't send that message
        //  until after logs have been updated (and not just info).  Perhaps even renaming message.
        
        // Give the service a couple seconds to process the update
        mHandler.postDelayed(mUpdateStatus, 2 * 1000);
    }
    };
    */

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = super.onCreateView(inflater, container, savedInstanceState);

        mInflater = inflater;

        View logs_header = View.inflate(activity.context, R.layout.logs_header, null);
        header_text = (TextView) logs_header.findViewById(R.id.header_text);
        ListView lv = (ListView) view.findViewById(android.R.id.list);
        lv.addHeaderView(logs_header, null, false);
        setHeaderText();
        setListAdapter(mAdapter);

        return view;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activity = (BatteryInfoActivity) getActivity();

        setHasOptionsMenu(true);
        setRetainInstance(true);

        convertF = activity.settings.getBoolean(SettingsActivity.KEY_CONVERT_F, false);
        col = new Col();

        logs = new LogDatabase(activity.context);
        completeCursor = logs.getAllLogs(false);
        timeDeltaCursor = new TimeDeltaCursor(completeCursor);
        filteredCursor = new FilteredCursor(timeDeltaCursor);

        mAdapter = new LogAdapter(activity.context, filteredCursor);

        serviceConnection = new BatteryInfoService.RemoteConnection(messenger);
        bindService();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (serviceConnected) {
            activity.context.unbindService(serviceConnection);
            serviceConnected = false;
        }
        completeCursor.close();
        logs.close();
    }

    @Override
    public void onResume() {
        super.onResume();
        //activity.context.registerReceiver(mBatteryInfoReceiver, batteryChangedFilter);
    }

    @Override
    public void onPause() {
        super.onPause();
        //activity.context.unregisterReceiver(mBatteryInfoReceiver);
    }

    public class ConfirmClearLogsDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return new AlertDialog.Builder(activity).setTitle(activity.res.getString(R.string.confirm_clear_logs))
                    .setPositiveButton(activity.res.getString(R.string.yes), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface di, int id) {
                            logs.clearAllLogs();
                            reloadList(false);
                            di.cancel();
                        }
                    }).setNegativeButton(activity.res.getString(R.string.cancel),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface di, int id) {
                                    di.cancel();
                                }
                            })
                    .create();
        }
    }

    public class ConfigureLogFilterDialogFragment extends DialogFragment {
        final boolean[] checked_items = new boolean[activity.str.log_filter_pref_keys.length];

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            for (int i = 0; i < checked_items.length; i++) {
                checked_items[i] = activity.settings.getBoolean(activity.str.log_filter_pref_keys[i], true);
            }

            return new AlertDialog.Builder(activity).setTitle(activity.res.getString(R.string.configure_log_filter))
                    .setMultiChoiceItems(R.array.log_filters, checked_items,
                            new DialogInterface.OnMultiChoiceClickListener() {
                                @Override
                                public void onClick(DialogInterface di, int id, boolean isChecked) {
                                    checked_items[id] = isChecked;
                                }
                            })
                    .setPositiveButton(activity.res.getString(R.string.okay),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface di, int id) {
                                    di.cancel(); // setFilters() is called in onCancel()
                                }
                            })
                    .create();
        }

        @Override
        public void onCancel(DialogInterface di) {
            setFilters(checked_items);
        }
    }

    private void setFilters(boolean[] checked_items) {
        SharedPreferences.Editor editor = activity.settings.edit();

        for (int i = 0; i < checked_items.length; i++) {
            editor.putBoolean(activity.str.log_filter_pref_keys[i], checked_items[i]);
        }

        editor.commit();

        reloadList(false);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);

        inflater.inflate(R.menu.logs, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);

        switch (filteredCursor.getCount()) {
        case 0:
            menu.findItem(R.id.menu_clear).setEnabled(false);
            menu.findItem(R.id.menu_export).setEnabled(false);
            menu.findItem(R.id.menu_reverse).setEnabled(false);
            break;
        case 1:
            menu.findItem(R.id.menu_clear).setEnabled(true);
            menu.findItem(R.id.menu_export).setEnabled(true);
            menu.findItem(R.id.menu_reverse).setEnabled(false);
            break;
        default:
            menu.findItem(R.id.menu_clear).setEnabled(true);
            menu.findItem(R.id.menu_export).setEnabled(true);
            menu.findItem(R.id.menu_reverse).setEnabled(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        DialogFragment df;

        switch (item.getItemId()) {
        case R.id.menu_clear:
            df = new ConfirmClearLogsDialogFragment();
            df.show(getFragmentManager(), "TODO: What is this string for?");

            return true;
        case R.id.menu_log_filter:
            df = new ConfigureLogFilterDialogFragment();
            df.show(getFragmentManager(), "TODO: What is this string for?2");

            return true;
        case R.id.menu_export:
            exportCSV();

            return true;
        case R.id.menu_reverse:
            reversed = (reversed) ? false : true;
            reloadList(true);

            return true;
        case android.R.id.home:
            startActivity(new Intent(activity, BatteryInfoActivity.class));
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private void reloadList(Boolean newQuery) {
        if (newQuery) {
            completeCursor.close();
            completeCursor = logs.getAllLogs(reversed);
            timeDeltaCursor = new TimeDeltaCursor(completeCursor);
            filteredCursor = new FilteredCursor(timeDeltaCursor);

            mAdapter.changeCursor(filteredCursor);
        } else {
            filteredCursor.requery();
        }

        setHeaderText();
    }

    private void setHeaderText() {
        int count = filteredCursor.getCount();

        if (count == 0)
            header_text.setText(activity.str.logs_empty);
        else
            header_text.setText(activity.str.n_log_items(count));
    }

    private void exportCSV() {
        String state = Environment.getExternalStorageState();

        if (state != null && state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
            Toast.makeText(activity.context, activity.str.read_only_storage, Toast.LENGTH_SHORT).show();
            return;
        } else if (state == null || !state.equals(Environment.MEDIA_MOUNTED)) {
            Toast.makeText(activity.context, activity.str.inaccessible_w_reason + state, Toast.LENGTH_SHORT).show();
            return;
        }

        Date d = new Date();
        String csvFileName = "BatteryIndicatorPro-Logs-" + d.getTime() + ".csv";

        File root = Environment.getExternalStorageDirectory();
        File csvFile = new File(root, csvFileName);

        String[] csvFields = { activity.str.date, activity.str.time, activity.str.status, activity.str.charge,
                activity.str.temperature, activity.str.temperature_f, activity.str.voltage };

        try {
            if (!csvFile.createNewFile() || !csvFile.canWrite()) {
                Toast.makeText(activity.context, activity.str.inaccessible_storage, Toast.LENGTH_SHORT).show();
                return;
            }

            BufferedWriter buf = new BufferedWriter(new FileWriter(csvFile));

            int cols = csvFields.length;
            int i;
            for (i = 0; i < cols; i++) {
                buf.write(csvFields[i]);
                if (i != cols - 1)
                    buf.write(",");
            }
            buf.write("\r\n");

            int statusCode;
            int[] statusCodes;
            int status, plugged, status_age;
            String s;

            for (completeCursor.moveToFirst(); !completeCursor.isAfterLast(); completeCursor.moveToNext()) {
                cols = CSV_ORDER.length;
                for (i = 0; i < cols; i++) {
                    if (CSV_ORDER[i].equals(LogDatabase.KEY_TIME)) {
                        d.setTime(completeCursor.getLong(mAdapter.timeIndex));
                        buf.write(mAdapter.dateFormat.format(d) + "," + mAdapter.timeFormat.format(d) + ",");
                    } else if (CSV_ORDER[i].equals(LogDatabase.KEY_STATUS_CODE)) {
                        statusCode = completeCursor.getInt(mAdapter.statusCodeIndex);
                        statusCodes = LogDatabase.decodeStatus(statusCode);
                        status = statusCodes[0];
                        plugged = statusCodes[1];
                        status_age = statusCodes[2];

                        if (status_age == LogDatabase.STATUS_OLD)
                            s = activity.str.log_statuses_old[status];
                        else
                            s = activity.str.log_statuses[status];
                        if (plugged > 0)
                            s += " " + activity.str.pluggeds[plugged];

                        buf.write(s + ",");
                    } else if (CSV_ORDER[i].equals(LogDatabase.KEY_CHARGE)) {
                        buf.write(String.valueOf(completeCursor.getInt(mAdapter.chargeIndex)) + ",");
                    } else if (CSV_ORDER[i].equals(LogDatabase.KEY_TEMPERATURE)) {
                        int temperature = completeCursor.getInt(mAdapter.temperatureIndex);
                        buf.write(String.valueOf(temperature / 10.0) + ",");
                        buf.write(String.valueOf(java.lang.Math.round(temperature * 9 / 5.0) / 10.0 + 32.0) + ",");
                    } else if (CSV_ORDER[i].equals(LogDatabase.KEY_VOLTAGE)) {
                        buf.write(String.valueOf(completeCursor.getInt(mAdapter.voltageIndex) / 1000.0));
                    }
                }
                buf.write("\r\n");
            }
            buf.close();
        } catch (Exception e) {
            Toast.makeText(activity.context, activity.str.inaccessible_storage, Toast.LENGTH_SHORT).show();
            return;
        }

        Toast.makeText(activity.context, activity.str.file_written, Toast.LENGTH_SHORT).show();
    }

    // Based on http://stackoverflow.com/a/7343721/1427098
    private class FilteredCursor extends CursorWrapper {
        private Cursor wrappedCursor;

        private ArrayList<Integer> shownIDs;
        private int len;
        private int pos;

        public FilteredCursor(Cursor cursor) {
            super(cursor);

            shownIDs = new ArrayList<Integer>();
            wrappedCursor = cursor;

            refilter();
        }

        public void refilter() {
            if (wrappedCursor.isClosed())
                return;

            shownIDs.clear();

            int wrappedCursorPos = wrappedCursor.getPosition();
            int statusCodeIndex = wrappedCursor.getColumnIndexOrThrow(LogDatabase.KEY_STATUS_CODE);

            boolean show_plugged_in = activity.settings.getBoolean("plugged_in", true);
            boolean show_unplugged = activity.settings.getBoolean("unplugged", true);
            boolean show_charging = activity.settings.getBoolean("charging", true);
            boolean show_discharging = activity.settings.getBoolean("discharging", true);
            boolean show_fully_charged = activity.settings.getBoolean("fully_charged", true);
            boolean show_unknown = activity.settings.getBoolean("unknown", true);

            for (wrappedCursor.moveToFirst(); !wrappedCursor.isAfterLast(); wrappedCursor.moveToNext()) {
                int statusCode = wrappedCursor.getInt(statusCodeIndex);
                int[] statusCodes = LogDatabase.decodeStatus(statusCode);
                int status = statusCodes[0];
                int plugged = statusCodes[1];
                int status_age = statusCodes[2];

                if (status == BatteryInfo.STATUS_FULLY_CHARGED && show_fully_charged) {
                    shownIDs.add(wrappedCursor.getPosition());
                } else if ((status == BatteryInfo.STATUS_UNKNOWN || status == BatteryInfo.STATUS_DISCHARGING
                        || status == BatteryInfo.STATUS_NOT_CHARGING || status > BatteryInfo.STATUS_MAX)
                        && show_unknown) {
                    shownIDs.add(wrappedCursor.getPosition());
                } else if (status_age == LogDatabase.STATUS_OLD) {
                    if ((status == BatteryInfo.STATUS_UNPLUGGED && show_discharging)
                            || (status == BatteryInfo.STATUS_CHARGING && show_charging))
                        shownIDs.add(wrappedCursor.getPosition());
                } else if (status_age == LogDatabase.STATUS_NEW) {
                    if ((status == BatteryInfo.STATUS_UNPLUGGED && show_unplugged)
                            || (status == BatteryInfo.STATUS_CHARGING && show_plugged_in))
                        shownIDs.add(wrappedCursor.getPosition());
                }
            }

            wrappedCursor.moveToPosition(wrappedCursorPos);

            len = shownIDs.size();
            pos = -1;
        }

        @Override
        public boolean requery() {
            boolean ret = super.requery();
            refilter();
            return ret;
        }

        @Override
        public int getCount() {
            return len;
        }

        @Override
        public boolean moveToPosition(int newPos) {
            boolean moved = super.moveToPosition(shownIDs.get(newPos));

            if (moved)
                pos = newPos;

            return moved;
        }

        @Override
        public final boolean move(int offset) {
            return moveToPosition(pos + offset);
        }

        @Override
        public final boolean moveToFirst() {
            return moveToPosition(0);
        }

        @Override
        public final boolean moveToLast() {
            return moveToPosition(len - 1);
        }

        @Override
        public final boolean moveToNext() {
            return moveToPosition(pos + 1);
        }

        @Override
        public final boolean moveToPrevious() {
            return moveToPosition(pos - 1);
        }

        @Override
        public final boolean isFirst() {
            return len != 0 && pos == 0;
        }

        @Override
        public final boolean isLast() {
            return len != 0 && pos == len - 1;
        }

        @Override
        public final boolean isBeforeFirst() {
            return len == 0 || pos == -1;
        }

        @Override
        public final boolean isAfterLast() {
            return len == 0 || pos == len;
        }

        @Override
        public int getPosition() {
            return pos;
        }
    }

    private class TimeDeltaCursor extends CursorWrapper {
        public static final String KEY_TIME_DELTA = "time_delta";

        private Cursor wrappedCursor;

        private int deltaColumnIndex;
        private String deltaColumnName = KEY_TIME_DELTA;

        private int statusCodeIndex, timeIndex;

        private long last_plugged, last_unplugged;

        private ArrayList<Long> deltas;

        public TimeDeltaCursor(Cursor cursor) {
            super(cursor);

            deltas = new ArrayList<Long>();
            wrappedCursor = cursor;

            deltaColumnIndex = super.getColumnCount();

            gen_deltas();
        }

        private void gen_delta() {
            long time = wrappedCursor.getLong(timeIndex);
            int statusCode = wrappedCursor.getInt(statusCodeIndex);
            int[] statusCodes = LogDatabase.decodeStatus(statusCode);
            int status = statusCodes[0];
            int plugged = statusCodes[1];
            int status_age = statusCodes[2];

            if (status == BatteryInfo.STATUS_FULLY_CHARGED) {
                if (last_plugged > 0)
                    deltas.add(time - last_plugged);
                else
                    deltas.add(-1l);
            } else if (status_age == LogDatabase.STATUS_NEW && status == BatteryInfo.STATUS_UNPLUGGED) {
                if (last_plugged > 0)
                    deltas.add(time - last_plugged);
                else
                    deltas.add(-1l);

                last_unplugged = time;
            } else if (status_age == LogDatabase.STATUS_NEW && status == BatteryInfo.STATUS_CHARGING) {
                if (last_unplugged > 0)
                    deltas.add(time - last_unplugged);
                else
                    deltas.add(-1l);

                last_plugged = time;
            } else {
                deltas.add(-1l);
            }
        }

        private boolean wrappedIsChronological() {
            if (wrappedCursor.getCount() < 2)
                return true;
            boolean chrono = true;

            int pos = wrappedCursor.getPosition();

            wrappedCursor.moveToFirst();
            long time1 = wrappedCursor.getLong(timeIndex);
            wrappedCursor.moveToNext();
            long time2 = wrappedCursor.getLong(timeIndex);

            while (time2 == time1 && !wrappedCursor.isAfterLast()) {
                wrappedCursor.moveToNext();
                time2 = wrappedCursor.getLong(timeIndex);
            }

            if (time2 < time1)
                chrono = false;

            wrappedCursor.moveToPosition(pos);

            return chrono;
        }

        public void gen_deltas() {
            if (wrappedCursor.isClosed())
                return;

            deltas.clear();

            last_plugged = -1;
            last_unplugged = -1;

            int wrappedCursorPos = wrappedCursor.getPosition();
            statusCodeIndex = wrappedCursor.getColumnIndexOrThrow(LogDatabase.KEY_STATUS_CODE);
            timeIndex = wrappedCursor.getColumnIndexOrThrow(LogDatabase.KEY_TIME);

            if (wrappedIsChronological())
                for (wrappedCursor.moveToFirst(); !wrappedCursor.isAfterLast(); wrappedCursor.moveToNext())
                    gen_delta();
            else
                for (wrappedCursor.moveToLast(); !wrappedCursor.isBeforeFirst(); wrappedCursor.moveToPrevious())
                    gen_delta();

            wrappedCursor.moveToPosition(wrappedCursorPos);
        }

        @Override
        public boolean requery() {
            boolean ret = super.requery();
            gen_deltas();
            return ret;
        }

        @Override
        public int getColumnCount() {
            return deltaColumnIndex + 1;
        }

        @Override
        public int getColumnIndex(String columnName) {
            if (deltaColumnName.equals(columnName))
                return deltaColumnIndex;
            else
                return super.getColumnIndex(columnName);
        }

        @Override
        public int getColumnIndexOrThrow(String columnName) throws java.lang.IllegalArgumentException {
            if (deltaColumnName.equals(columnName))
                return deltaColumnIndex;
            else
                return super.getColumnIndexOrThrow(columnName);
        }

        @Override
        public String getColumnName(int columnIndex) {
            if (columnIndex == deltaColumnIndex)
                return deltaColumnName;
            else
                return super.getColumnName(columnIndex);
        }

        @Override
        public String[] getColumnNames() {
            String[] a = super.getColumnNames();
            String[] b = new String[a.length + 1];

            for (int i = 0; i < a.length; i++)
                b[i] = a[i];

            b[a.length] = deltaColumnName;

            return b;
        }

        @Override
        public long getLong(int columnIndex) {
            if (columnIndex == deltaColumnIndex) {
                int pos = getPosition();

                if (!wrappedIsChronological())
                    pos = getCount() - 1 - pos;

                return deltas.get(pos);
            } else
                return super.getLong(columnIndex);
        }

        // TODO: This was introduced in API 11.  It seems to be safely ignored by APIs less than 11, other than
        //         taking a while for the verifier to no-op it.  Is it worth overriding to add only to
        //         API 11+ devices?  I think it only slows things down on the first run, and not noticeably on most devices.
        /*
        @Override
        public int getType(int columnIndex) {
        if (columnIndex == deltaColumnIndex)
            return Cursor.FIELD_TYPE_INTEGER;
        else
            return super.getType(columnIndex);
        }
        */

        @Override
        public boolean isNull(int columnIndex) {
            if (columnIndex == deltaColumnIndex)
                return false;
            else
                return super.isNull(columnIndex);
        }
    }

    private class LogAdapter extends CursorAdapter {
        public int statusCodeIndex, chargeIndex, timeIndex, temperatureIndex, voltageIndex, timeDeltaIndex;
        public DateFormat dateFormat, timeFormat;

        private Date d = new Date();

        public LogAdapter(Context context, Cursor cursor) {
            super(context, cursor);

            dateFormat = android.text.format.DateFormat.getDateFormat(context);
            timeFormat = android.text.format.DateFormat.getTimeFormat(context);

            statusCodeIndex = cursor.getColumnIndexOrThrow(LogDatabase.KEY_STATUS_CODE);
            chargeIndex = cursor.getColumnIndexOrThrow(LogDatabase.KEY_CHARGE);
            timeIndex = cursor.getColumnIndexOrThrow(LogDatabase.KEY_TIME);
            temperatureIndex = cursor.getColumnIndexOrThrow(LogDatabase.KEY_TEMPERATURE);
            voltageIndex = cursor.getColumnIndexOrThrow(LogDatabase.KEY_VOLTAGE);
            timeDeltaIndex = cursor.getColumnIndexOrThrow(TimeDeltaCursor.KEY_TIME_DELTA);
        }

        public View newView(Context context, Cursor cursor, ViewGroup parent) {
            return mInflater.inflate(R.layout.log_item, parent, false);
        }

        public void bindView(View view, Context context, Cursor cursor) {
            TextView status_tv = (TextView) view.findViewById(R.id.status);
            TextView percent_tv = (TextView) view.findViewById(R.id.percent);
            TextView time_tv = (TextView) view.findViewById(R.id.time);
            TextView temp_volt_tv = (TextView) view.findViewById(R.id.temp_volt);
            TextView time_diff_tv = (TextView) view.findViewById(R.id.time_diff);

            int statusCode = cursor.getInt(statusCodeIndex);
            int[] statusCodes = LogDatabase.decodeStatus(statusCode);
            int status = statusCodes[0];
            int plugged = statusCodes[1];
            int status_age = statusCodes[2];

            String s;

            if (status_age == LogDatabase.STATUS_OLD) {
                status_tv.setTextColor(col.old_status);
                percent_tv.setTextColor(col.old_status);
                s = activity.str.log_statuses_old[status];

                time_diff_tv.setVisibility(View.GONE);
            } else {
                switch (status) {
                case 0:
                    status_tv.setTextColor(col.unplugged);
                    percent_tv.setTextColor(col.unplugged);
                    break;
                case 2:
                    status_tv.setTextColor(col.plugged);
                    percent_tv.setTextColor(col.plugged);
                    break;
                case 5:
                    status_tv.setTextColor(col.charged);
                    percent_tv.setTextColor(col.charged);
                    break;
                default:
                    status_tv.setTextColor(col.unknown);
                    percent_tv.setTextColor(col.unknown);
                }

                s = activity.str.log_statuses[status];
                long delta;

                switch (status) {
                case 0:
                case 5:
                    delta = cursor.getLong(timeDeltaIndex);

                    if (delta < 0) {
                        time_diff_tv.setVisibility(View.GONE);
                        break;
                    }

                    time_diff_tv.setText(String.format(activity.res.getString(R.string.after_n_hours_plugged_in),
                            delta / 1000.0 / 60.0 / 60.0));

                    time_diff_tv.setVisibility(View.VISIBLE);
                    break;
                case 2:
                    delta = cursor.getLong(timeDeltaIndex);

                    if (delta < 0) {
                        time_diff_tv.setVisibility(View.GONE);
                        break;
                    }

                    time_diff_tv.setText(String.format(activity.res.getString(R.string.after_n_hours_unplugged),
                            delta / 1000.0 / 60.0 / 60.0));

                    time_diff_tv.setVisibility(View.VISIBLE);
                    break;
                default:
                    time_diff_tv.setVisibility(View.GONE);
                }
            }

            if (plugged > 0)
                s += " " + activity.str.pluggeds[plugged];

            status_tv.setText(s);

            percent_tv.setText("" + cursor.getInt(chargeIndex) + "%");

            d.setTime(cursor.getLong(timeIndex));
            time_tv.setText(dateFormat.format(d) + "  " + timeFormat.format(d));

            int temperature = cursor.getInt(temperatureIndex);
            if (temperature != 0)
                temp_volt_tv.setText("" + activity.str.formatTemp(temperature, convertF));
            else
                temp_volt_tv.setText(""); /* TextViews are reused */

            int voltage = cursor.getInt(voltageIndex);
            if (voltage != 0)
                temp_volt_tv.setText(
                        ((String) temp_volt_tv.getText().toString()) + " / " + activity.str.formatVoltage(voltage));
        }
    }

    private class Col {
        public int old_status;
        public int charged;
        public int plugged;
        public int unplugged;
        public int unknown;

        public Col() {
            old_status = activity.res.getColor(R.color.log_old_status);
            charged = activity.res.getColor(R.color.log_charged);
            plugged = activity.res.getColor(R.color.log_plugged);
            unplugged = activity.res.getColor(R.color.log_unplugged);
            unknown = activity.res.getColor(R.color.log_unknown);
        }
    }
}