Android Open Source - callmeter Log Runner Service






From Project

Back to project page callmeter.

License

The source code is released under:

GNU General Public License

If you think the Android project callmeter listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2009-2013 Felix Bechstein
 * //from www  .  j  a  v  a 2  s  . c o m
 * This file is part of CallMeter 3G.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; If not, see <http://www.gnu.org/licenses/>.
 */
package de.ub0r.android.callmeter.data;

import android.app.IntentService;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteException;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.preference.DatePreference;
import android.preference.PreferenceManager;
import android.provider.CallLog.Calls;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.SparseArray;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import de.ub0r.android.callmeter.BuildConfig;
import de.ub0r.android.callmeter.CallMeter;
import de.ub0r.android.callmeter.ui.AskForPlan;
import de.ub0r.android.callmeter.ui.Common;
import de.ub0r.android.callmeter.ui.Plans;
import de.ub0r.android.callmeter.ui.prefs.Preferences;
import de.ub0r.android.callmeter.widget.LogsAppWidgetProvider;
import de.ub0r.android.callmeter.widget.StatsAppWidgetProvider;
import de.ub0r.android.lib.Utils;
import de.ub0r.android.lib.apis.Contact;
import de.ub0r.android.logg0r.Log;

/**
 * Run logs in background.
 *
 * @author flx
 */
public final class LogRunnerService extends IntentService {

    /** Tag for output. */
    private static final String TAG = "LogRunnerService";

    /** Minimum amount of unmatched logs to start showing the dialog. */
    private static final int UNMATHCEDLOGS_TO_SHOW_DIALOG = 50;

    /** {@link Uri} to all threads. */
    private static final Uri URI_THREADS = Uri.parse("content://mms-sms/conversations").buildUpon()
            .appendQueryParameter("simple", "true").build();
    /** {@link Uri} to all sms. */
    private static final Uri URI_SMS = Uri.parse("content://sms/");
    /** {@link Uri} to all mms. */
    private static final Uri URI_MMS = Uri.parse("content://mms/");

    /** Projection for threads table. */
    private static final String[] THREADS_PROJ = new String[]{"recipient_ids"};

    /** {@link HashMap} mapping threads to numbers. */
    private static final SparseArray<String> THREAD_TO_NUMBER = new SparseArray<String>();

    /** {@link Intent}'s action for run matcher. */
    public static final String ACTION_RUN_MATCHER = "de.ub0r.android.callmeter.RUN_MATCHER";
    /** {@link Intent}'s action for short run. */
    public static final String ACTION_SHORT_RUN = "de.ub0r.android.callmeter.SHORT_RUN";
    /** {@link Intent}'s action for receiving SMS. */
    private static final String ACTION_SMS = "android.provider.Telephony.SMS_RECEIVED";

    /** Prefix for store of last data. */
    private static final String PREFS_LASTDATA_PREFIX = "last_data_";

    /** Thread Id. */
    private static final String THRADID = "thread_id";
    /** Type for mms. */
    private static final String MMS_TYPE = "m_type";
    /** Type for incoming mms. */
    private static final int MMS_IN = 132;
    /** Type for outgoing mms. */
    private static final int MMS_OUT = 128;

    /** Length of an SMS. */
    private static final int SMS_LENGTH = 160;

    /** Is phone roaming? */
    private static boolean roaming = false;
    /** My own number. */
    private static String mynumber = null;
    /** Split messages at 160chars. */
    private static boolean splitAt160 = false;

    /** Ignore logs before. */
    private static long dateStart = 0L;

    /** Delete logs before that date. */
    private static long deleteBefore = -1L;

    /** Minimal difference of traffic which will get saved. */
    private static final long DATA_MIN_DIFF = 1024L * 512L;

    /** Time to wait for logs after hanging up. */
    private static final long WAIT_FOR_LOGS = 1500L;
    /** Maximum gap for logs. */
    private static final long GAP_FOR_LOGS = 10000L;

    /** Minimal time between updates. */
    private static final long MIN_DELAY = 5000L;
    /** Action of last call. */
    private static String lastAction = null;
    /** Time of last call. */
    private static long lastUpdate = 0L;

    private static boolean inUpdate = false;

    /** Service's {@link Handler}. */
    private Handler handler = null;

    /**
     * Default Constructor.
     */
    public LogRunnerService() {
        super("LogRunner");
    }

    /**
     * Run {@link LogRunnerService}.
     *
     * @param action  original action sent to {@link LogRunnerReceiver}
     * @param context {@link Context}
     */
    public static void update(final Context context, final String action) {
        Log.d(TAG, "update(", action, ")");
        long now = System.currentTimeMillis();
        if (inUpdate) {
            Log.i(TAG, "skip update(" + action + "): still updating");
        } else if ((action == null && lastAction != null)
                || (action != null && !action.equals(lastAction)) || lastUpdate == 0L
                || now > lastUpdate + MIN_DELAY) {
            context.startService(new Intent(action, null, context, LogRunnerService.class));
            lastAction = action;
            lastUpdate = now;
        } else {
            Log.i(TAG, "skip update(" + action + "): " + now + "<=" + (lastUpdate + MIN_DELAY));
        }
    }

    /**
     * Fix MMS date.
     *
     * @param date date in millis or seconds
     * @return date in millis
     */
    private static long fixDate(final long date) {
        if (date < CallMeter.MIN_DATE) {
            return date * CallMeter.MILLIS;
        } else {
            return date;
        }
    }

    /**
     * Get maximum date of logs type %.
     *
     * @param cr   {@link ContentResolver}
     * @param type type
     * @return maximum date found. -1 if nothing was found.
     */
    private static long getMaxDate(final ContentResolver cr, final int type) {
        Log.d(TAG, "getMaxDate(", type, ")");
        final Cursor cursor = cr.query(DataProvider.Logs.CONTENT_URI,
                new String[]{DataProvider.Logs.DATE}, DataProvider.Logs.TYPE + " = ?",
                new String[]{String.valueOf(type)}, DataProvider.Logs.DATE + " DESC LIMIT 1");
        long maxdate = deleteBefore;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                maxdate = cursor.getLong(0);
                Log.d(TAG, "maxdate=", maxdate);
            }
            cursor.close();
        }
        if (maxdate > dateStart) {
            return maxdate;
        }
        Log.d(TAG, "getMaxDate(): dateStart=", dateStart);
        return dateStart;
    }

    /**
     * Get maximum date of logs type %.
     *
     * @param cr        {@link ContentResolver}
     * @param type      type
     * @param direction direction
     * @return maximum date found. -1 if nothing was found.
     */
    private static long getMaxDate(final ContentResolver cr, final int type, final int direction) {
        Log.d(TAG, "getMaxDate(", type, ",", direction, ")");
        final Cursor cursor = cr.query(DataProvider.Logs.CONTENT_URI,
                new String[]{DataProvider.Logs.DATE}, DataProvider.Logs.TYPE + " = ? AND "
                        + DataProvider.Logs.DIRECTION + " = ?", new String[]{
                        String.valueOf(type), String.valueOf(direction)}, DataProvider.Logs.DATE
                        + " DESC LIMIT 1"
        );
        long maxdate = deleteBefore;
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                maxdate = cursor.getLong(0);
            }
            cursor.close();
        }
        if (maxdate > dateStart) {
            Log.d(TAG, "getMaxDate(): ", maxdate);
            return maxdate;
        }
        Log.d(TAG, "getMaxDate(): ", dateStart);
        return dateStart;
    }

    /**
     * Get last amount from Logs.
     *
     * @param p         {@link SharedPreferences}
     * @param type      type
     * @param direction direction
     * @return amount of last log entry
     */
    private static long getLastData(final SharedPreferences p, final int type,
            final int direction) {
        Log.d(TAG, "getLastData(p,", type, ",", direction, ")");
        long l = p.getLong(PREFS_LASTDATA_PREFIX + type + "_" + direction, 0L);
        Log.d(TAG, "getLastData(): ", l);
        return l;
    }

    /**
     * Get the last record for some kind of data; only unmatched log records are returned!
     *
     * @param cr        {@link ContentResolver}
     * @param type      type
     * @param direction direction
     * @return {@link Cursor} of last log record; column 0=id,1=amount
     */
    private static Cursor getLastData(final ContentResolver cr, final int type,
            final int direction) {
        Cursor c = cr.query(DataProvider.Logs.CONTENT_URI, new String[]{DataProvider.Logs.ID,
                        DataProvider.Logs.AMOUNT, DataProvider.Logs.PLAN_ID,
                        DataProvider.Logs.ROAMED},
                DataProvider.Logs.TYPE + " = ? AND " + DataProvider.Logs.DIRECTION + " = ?",
                new String[]{String.valueOf(type), String.valueOf(direction)},
                DataProvider.Logs.DATE + " DESC LIMIT 1"
        );
        if (c == null) {
            return null;
        }
        if (c.moveToFirst()) {
            int pid = c.getInt(2);
            if (pid != DataProvider.NO_ID && pid != DataProvider.NOT_FOUND
                    || roaming != (c.getInt(3) == 1)) {
                c.close();
                return null;
            }

            return c;
        }
        if (!c.isClosed()) {
            c.close();
        }
        return null;
    }

    /**
     * Set last amount from Logs.
     *
     * @param e         {@link Editor}
     * @param type      type
     * @param direction direction
     * @param amount    amount
     */
    public static void setLastData(final Editor e, final int type, final int direction,
            final long amount) {
        if (amount < 0L) {
            e.remove(PREFS_LASTDATA_PREFIX + type + "_" + direction);
        } else {
            e.putLong(PREFS_LASTDATA_PREFIX + type + "_" + direction, amount);
        }
    }

    /**
     * Run logs: data.
     *
     * @param context     {@link Context}
     * @param forceUpdate force update
     */
    private static void updateData(final Context context, final boolean forceUpdate) {
        Log.d(TAG, "updateData(", forceUpdate, ")");
        boolean doUpdate = forceUpdate;
        final SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(context);
        final ContentResolver cr = context.getContentResolver();

        if (!doUpdate) {
            long dateLastRx = getMaxDate(cr, DataProvider.TYPE_DATA, DataProvider.DIRECTION_IN);
            long dateLastTx = getMaxDate(cr, DataProvider.TYPE_DATA, DataProvider.DIRECTION_OUT);
            final long updateinterval = Utils.parseLong(
                    p.getString(Preferences.PREFS_UPDATE_INTERVAL,
                            String.valueOf(LogRunnerReceiver.DELAY)), LogRunnerReceiver.DELAY
            )
                    * LogRunnerReceiver.DELAY_FACTOR;
            final long n = System.currentTimeMillis();
            if (n - dateLastRx > updateinterval / 2) {
                doUpdate = true;
            }
            if (n - dateLastTx > updateinterval / 2) {
                doUpdate = true;
            }
        }

        final long lastRx = getLastData(p, DataProvider.TYPE_DATA, DataProvider.DIRECTION_IN);
        final long lastTx = getLastData(p, DataProvider.TYPE_DATA, DataProvider.DIRECTION_OUT);

        final Device d = Device.getDevice();
        try {
            final long rx = d.getCellRxBytes();
            final long tx = d.getCellTxBytes();
            Log.d(TAG, "rx: ", rx);
            Log.d(TAG, "tx: ", tx);
            if (rx > 0L || tx > 0L) {
                long rrx = rx;
                long rtx = tx;
                if (rx >= lastRx && tx >= lastTx) {
                    rrx = rx - lastRx;
                    rtx = tx - lastTx;
                }
                Log.d(TAG, "rrx: ", rrx);
                Log.d(TAG, "ttx: ", rtx);

                if (doUpdate || rrx > DATA_MIN_DIFF || rtx > DATA_MIN_DIFF) {
                    // save data to db
                    final ContentValues baseCv = new ContentValues();
                    baseCv.put(DataProvider.Logs.PLAN_ID, DataProvider.NO_ID);
                    baseCv.put(DataProvider.Logs.RULE_ID, DataProvider.NO_ID);
                    baseCv.put(DataProvider.Logs.TYPE, DataProvider.TYPE_DATA);
                    baseCv.put(DataProvider.Logs.DATE, System.currentTimeMillis());
                    if (roaming) {
                        baseCv.put(DataProvider.Logs.ROAMED, 1);
                    }
                    if (!TextUtils.isEmpty(mynumber)) {
                        baseCv.put(DataProvider.Logs.MYNUMBER, mynumber);
                    }

                    ContentValues cv;
                    Editor e = p.edit();
                    boolean dirty = false;
                    boolean update = rrx > 0;
                    Cursor c = null;
                    if (update && forceUpdate && rrx <= DATA_MIN_DIFF) {
                        c = getLastData(cr, DataProvider.TYPE_DATA, DataProvider.DIRECTION_IN);
                        Log.d(TAG, "getLastData()=", c);
                    }
                    if (update && c == null) {
                        cv = new ContentValues(baseCv);
                        cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_IN);
                        cv.put(DataProvider.Logs.AMOUNT, rrx);
                        cr.insert(DataProvider.Logs.CONTENT_URI, cv);
                    } else if (c != null) {
                        Log.d(TAG, "force rx update: ", rrx);
                        cv = new ContentValues(baseCv);
                        cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_IN);
                        cv.put(DataProvider.Logs.AMOUNT, rrx + c.getLong(1));
                        cr.update(DataProvider.Logs.CONTENT_URI, cv, DataProvider.Logs.ID + "=?",
                                new String[]{c.getString(0)});
                        c.close();
                    } else {
                        Log.d(TAG, "skip rx: ", rrx);
                    }
                    if (update) {
                        setLastData(e, DataProvider.TYPE_DATA, DataProvider.DIRECTION_IN, rx);
                        dirty = true;
                    }

                    update = rtx > 0;
                    c = null;
                    if (update && forceUpdate && rtx <= DATA_MIN_DIFF) {
                        c = getLastData(cr, DataProvider.TYPE_DATA, DataProvider.DIRECTION_OUT);
                        Log.d(TAG, "getLastData()=", c);
                    }
                    if (update && c == null) {
                        cv = new ContentValues(baseCv);
                        cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_OUT);
                        cv.put(DataProvider.Logs.AMOUNT, rtx);
                        cr.insert(DataProvider.Logs.CONTENT_URI, cv);
                    } else if (c != null) {
                        Log.d(TAG, "force tx update: ", rtx);
                        cv = new ContentValues(baseCv);
                        cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_OUT);
                        cv.put(DataProvider.Logs.AMOUNT, rtx + c.getLong(1));
                        cr.update(DataProvider.Logs.CONTENT_URI, cv, DataProvider.Logs.ID + "=?",
                                new String[]{c.getString(0)});
                        c.close();
                    } else {
                        Log.d(TAG, "skip tx: ", rtx);
                    }
                    if (update) {
                        setLastData(e, DataProvider.TYPE_DATA, DataProvider.DIRECTION_OUT, tx);
                        dirty = true;
                    }
                    if (dirty) {
                        e.commit();
                    }
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "I/O Error", e);
        }
        Log.d(TAG, "updateData(): done");
    }

    private static void printColumn(final Cursor c, int n) {
        Log.i(TAG, "---------- column - start ----: ", n);
        int l = c.getColumnCount();
        for (int i = 0; i < l; ++i) {
            Log.i(TAG, c.getColumnName(i), ": ", c.getString(i));
        }
        Log.i(TAG, "---------- column - end ------: ", n);
    }

    /** Get column id holding sim_id, simid or whatever. */
    public static int getSimIdColumn(final Cursor c) {
        if (c == null) {
            return -1;
        }
        for (String s : new String[]{"sim_id", "simid", "sub_id", "sim_slot"}) {
            int id = c.getColumnIndex(s);
            if (id >= 0) {
                Log.d(TAG, "sim_id column found: ", s);
                return id;
            }
        }
        if (BuildConfig.DEBUG_LOG) {
            Log.i(TAG, "table schema for cursor: ", c);
            int l = c.getColumnCount();
            Log.i(TAG, "column count: ", l);
            for (int i = 0; i < l; ++i) {
                Log.i(TAG, "column: ", c.getColumnName(i));
            }
        }
        Log.d(TAG, "no sim_id column found");
        return -1;
    }

    private static int getSecondSimId(final ContentResolver cr, final Uri uri) {
        try {
            int secondSimId = -1;
            Cursor c = cr.query(uri, null, "1=2", null, null);
            assert c != null;
            int id = getSimIdColumn(c);
            if (id < 0) {
                return -1;
            }
            String name = c.getColumnName(id);
            c.close();
            c = cr.query(uri, null, name + ">0", null, name + " DESC");
            assert c != null;
            if (c.moveToFirst()) {
                secondSimId = c.getInt(id);
            }
            c.close();
            Log.d(TAG, "second sim id: ", uri, ": ", secondSimId);
            return secondSimId;
        } catch (SQLiteException e) {
            Log.w(TAG, "sim_id check for calls failed", e);
            return -1;
        }
    }

    /**
     * Get maximum sim id we can find.
     */
    public static int getSecondCombinedSimId(final ContentResolver cr) {
        return Math.max(
                getSecondSimId(cr, Calls.CONTENT_URI),
                getSecondSimId(cr, URI_SMS)
        );
    }

    /** Check, if there is dual sim support. */
    public static boolean checkCallsSimIdColumn(final ContentResolver cr) {
        try {
            String where = BuildConfig.DEBUG_LOG ? null : "1=2";
            Cursor c = cr.query(Calls.CONTENT_URI, null, where, null, null);
            boolean check = false;
            if (c != null) {
                check = getSimIdColumn(c) >= 0;
                c.close();
            }
            Log.i(TAG, "sim_id column found in calls database: " + check);
            return check;
        } catch (SQLiteException e) {
            Log.w(TAG, "sim_id check for calls failed", e);
            return false;
        }
    }

    /** Check, if there is dual sim support. */
    public static boolean checkSmsSimIdColumn(final ContentResolver cr) {
        try {
            String where = BuildConfig.DEBUG_LOG ? null : "1=2";
            Cursor c = cr.query(URI_SMS, null, where, null, null);
            boolean check = false;
            if (c != null) {
                check = getSimIdColumn(c) >= 0;
                c.close();
            }
            Log.i(TAG, "sim_id column found in sms database: " + check);
            return check;
        } catch (SQLiteException e) {
            Log.w(TAG, "sim_id check for sms failed", e);
            return false;
        }
    }

    /**
     * Run logs: calls.
     */
    private static void updateCalls(final Context context) {
        Log.d(TAG, "updateCalls()");
        final ContentResolver cr = context.getContentResolver();
        SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(context);
        long maxdate = Math.max(getLastData(p, DataProvider.TYPE_CALL, 0),
                getMaxDate(cr, DataProvider.TYPE_CALL));
        Log.d(TAG, "maxdate: ", maxdate);
        Cursor cursor;
        try {
            cursor = cr.query(Calls.CONTENT_URI, null, Calls.DATE + " > ?",
                    new String[]{String.valueOf(maxdate)}, Calls.DATE + " DESC");
        } catch (SQLException e) {
            Log.e(TAG, "updateCalls(): SQLE", e);
            cursor = cr.query(Calls.CONTENT_URI, new String[]{Calls.TYPE, Calls.DURATION,
                            Calls.DATE, Calls.NUMBER}, Calls.DATE + " > ?",
                    new String[]{String.valueOf(maxdate)}, Calls.DATE + " DESC"
            );
        } catch (NullPointerException e) {
            Log.e(TAG, "updateCalls(): NPE", e);
            return;
        }
        if (cursor == null) {
            Log.d(TAG, "updateCalls(): null");
            return;
        }
        Log.d(TAG, "cursor: ", cursor.getCount());
        if (cursor.moveToFirst()) {
            final int idType = cursor.getColumnIndex(Calls.TYPE);
            final int idDuration = cursor.getColumnIndex(Calls.DURATION);
            final int idDate = cursor.getColumnIndex(Calls.DATE);
            final int idNumber = cursor.getColumnIndex(Calls.NUMBER);
            final int idSimId = getSimIdColumn(cursor);

            final ArrayList<ContentValues> cvalues = new ArrayList<ContentValues>(
                    CallMeter.HUNDRET);
            int i = 0;
            do {
                if (BuildConfig.DEBUG_LOG && i < 30) {
                    printColumn(cursor, i);
                    i += 1;
                }
                final ContentValues cv = new ContentValues();
                final int t = cursor.getInt(idType);
                if (t == Calls.INCOMING_TYPE) {
                    cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_IN);
                } else if (t == Calls.OUTGOING_TYPE) {
                    cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_OUT);
                } else {
                    Log.w(TAG, "ignore unknown direction");
                    continue;
                }
                final int d = cursor.getInt(idDuration);
                if (d == 0) {
                    Log.i(TAG, "ignore duration=0");
                    continue;
                }
                long l = cursor.getLong(idDate);
                if (l > maxdate) {
                    maxdate = l;
                }
                cv.put(DataProvider.Logs.PLAN_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.RULE_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.TYPE, DataProvider.TYPE_CALL);
                cv.put(DataProvider.Logs.DATE, l);
                cv.put(DataProvider.Logs.REMOTE,
                        DataProvider.Logs.cleanNumber(cursor.getString(idNumber), false));
                cv.put(DataProvider.Logs.AMOUNT, d);
                if (roaming) {
                    cv.put(DataProvider.Logs.ROAMED, 1);
                }
                if (idSimId >= 0) {
                    cv.put(DataProvider.Logs.MYNUMBER, cursor.getString(idSimId));
                } else if (!TextUtils.isEmpty(mynumber)) {
                    cv.put(DataProvider.Logs.MYNUMBER, mynumber);
                }
                cvalues.add(cv);
                if (cvalues.size() >= CallMeter.HUNDRET) {
                    cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                            cvalues.toArray(new ContentValues[cvalues.size()]));
                    Log.d(TAG, "new calls: ", cvalues.size());
                    cvalues.clear();
                }
            } while (cursor.moveToNext());
            if (cvalues.size() > 0) {
                cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                        cvalues.toArray(new ContentValues[cvalues.size()]));
                Log.d(TAG, "new calls: ", cvalues.size());
                Editor e = p.edit();
                setLastData(e, DataProvider.TYPE_CALL, 0, maxdate);
                e.commit();
            }
        }
        cursor.close();
        Log.d(TAG, "updateCalls(): done");
    }

    /**
     * Run logs: sms.
     *
     * @param cr        {@link ContentResolver}
     * @param direction direction
     */
    private static void updateSMS(final ContentResolver cr, final int direction) {
        Log.d(TAG, "updateSMS(cr,", direction, ")");
        int type = Calls.OUTGOING_TYPE;
        if (direction == DataProvider.DIRECTION_IN) {
            type = Calls.INCOMING_TYPE;
        }

        final long maxdate = getMaxDate(cr, DataProvider.TYPE_SMS, direction);
        final String[] smsProjection = new String[]{Calls.DATE, Calls.TYPE, "address", "body"};
        Cursor cursor;
        try {
            try {
                cursor = cr.query(URI_SMS, null, Calls.DATE + " > ? and " + Calls.TYPE + " = ?",
                        new String[]{String.valueOf(maxdate), String.valueOf(type)}, Calls.DATE
                                + " DESC"
                );
            } catch (SQLException e) {
                Log.e(TAG, "updateCalls(): SQLE", e);
                cursor = cr.query(URI_SMS, smsProjection, Calls.DATE + " > ? and " + Calls.TYPE
                                + " = ?",
                        new String[]{String.valueOf(maxdate), String.valueOf(type)},
                        Calls.DATE + " DESC"
                );
            }
        } catch (NullPointerException e) {
            Log.e(TAG, "updateSMS(): NPE", e);
            return;
        }
        if (cursor == null) {
            Log.d(TAG, "updateSMS(): null");
            return;
        }
        Log.d(TAG, "cursor: ", cursor.getCount());
        if (cursor.moveToFirst()) {
            final int idDate = cursor.getColumnIndex(Calls.DATE);
            final int idAddress = cursor.getColumnIndex("address");
            final int idBody = cursor.getColumnIndex("body");
            final int idSimId = getSimIdColumn(cursor);
            final ArrayList<ContentValues> cvalues = new ArrayList<ContentValues>(
                    CallMeter.HUNDRET);
            int i = 0;
            do {
                if (BuildConfig.DEBUG_LOG && i < 30) {
                    printColumn(cursor, i);
                    i += 1;
                }
                final ContentValues cv = new ContentValues();
                cv.put(DataProvider.Logs.DIRECTION, direction);
                cv.put(DataProvider.Logs.PLAN_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.RULE_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.TYPE, DataProvider.TYPE_SMS);
                cv.put(DataProvider.Logs.DATE, cursor.getLong(idDate));
                Log.d(TAG, "date: ", cursor.getLong(idDate));
                cv.put(DataProvider.Logs.REMOTE,
                        DataProvider.Logs.cleanNumber(cursor.getString(idAddress), false));
                final String body = cursor.getString(idBody);
                int l = 1;
                if (!TextUtils.isEmpty(body)) {
                    Log.d(TAG, "body: ", body.replaceAll("[a-z]", "x").replaceAll("[A-Z]", "X"));
                    if (splitAt160) {
                        l = ((body.length() - 1) / SMS_LENGTH) + 1;
                    } else {
                        try {
                            l = SmsMessage.calculateLength(body, false)[0];
                        } catch (NullPointerException e) {
                            Log.e(TAG, "error getting length for message: " + body, e);
                            l = ((body.length() - 1) / SMS_LENGTH) + 1;
                        } catch (RuntimeException e) {
                            Log.e(TAG, "SMS app not available", e);
                            l = ((body.length() - 1) / SMS_LENGTH) + 1;
                        }
                        Log.d(TAG, "body length: ", l);
                    }
                }
                cv.put(DataProvider.Logs.AMOUNT, l);
                if (roaming) {
                    cv.put(DataProvider.Logs.ROAMED, 1);
                }
                if (idSimId >= 0) {
                    cv.put(DataProvider.Logs.MYNUMBER, cursor.getString(idSimId));
                } else if (!TextUtils.isEmpty(mynumber)) {
                    cv.put(DataProvider.Logs.MYNUMBER, mynumber);
                }
                cvalues.add(cv);
                if (cvalues.size() >= CallMeter.HUNDRET) {
                    cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                            cvalues.toArray(new ContentValues[cvalues.size()]));
                    Log.d(TAG, "new sms: ", cvalues.size());
                    cvalues.clear();
                }
            } while (cursor.moveToNext());
            if (cvalues.size() > 0) {
                cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                        cvalues.toArray(new ContentValues[cvalues.size()]));
                Log.d(TAG, "new sms: ", cvalues.size());
            }
        }
        cursor.close();
        Log.d(TAG, "updateSMS(): done");
    }

    /**
     * Run logs: mms.
     *
     * @param context {@link Context}
     */
    private static void updateMMS(final Context context) {
        Log.d(TAG, "updateMMS()");
        final ContentResolver cr = context.getContentResolver();
        final long maxdate = getMaxDate(cr, DataProvider.TYPE_MMS);
        final String[] mmsProjection = new String[]{Calls.DATE, MMS_TYPE, THRADID};
        Cursor cursor;
        try {
            cursor = cr.query(URI_MMS, mmsProjection, Calls.DATE + " > ?",
                    new String[]{String.valueOf(maxdate)}, Calls.DATE + " DESC");
        } catch (NullPointerException e) {
            Log.e(TAG, "updateMMS(): NPE", e);
            return;
        }
        if (cursor == null) {
            Log.d(TAG, "updateMMS(): null");
            return;
        }
        if (!cursor.moveToFirst()) {
            cursor.close();
            cursor = cr.query(URI_MMS, mmsProjection, Calls.DATE + " > "
                    + (maxdate / CallMeter.MILLIS), null, Calls.DATE + " DESC");
        }
        if (cursor == null) {
            Log.d(TAG, "updateMMS(): null");
            return;
        }
        Log.d(TAG, "cursor: ", cursor.getCount());
        if (cursor.moveToFirst()) {
            final int idDate = cursor.getColumnIndex(Calls.DATE);
            final int idType = cursor.getColumnIndex(MMS_TYPE);
            final int idThId = cursor.getColumnIndex(THRADID);
            final int idSimId = cursor.getColumnIndex("sim_id");

            final ArrayList<ContentValues> cvalues = new ArrayList<ContentValues>(
                    CallMeter.HUNDRET);
            do {
                final ContentValues cv = new ContentValues();
                final int t = cursor.getInt(idType);
                final long d = cursor.getLong(idDate);
                Log.d(TAG, "mms date: ", d);
                Log.d(TAG, "mms type: ", t);
                if (t == MMS_IN) {
                    cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_IN);
                } else if (t == MMS_OUT) {
                    cv.put(DataProvider.Logs.DIRECTION, DataProvider.DIRECTION_OUT);
                } else {
                    continue;
                }
                final int tid = cursor.getInt(idThId);
                Log.d(TAG, "thread_id: ", tid);
                if (tid >= 0L) {
                    String n = THREAD_TO_NUMBER.get(tid);
                    if (n == null) {
                        final Cursor c = cr.query(URI_THREADS, THREADS_PROJ, "_id = ?",
                                new String[]{String.valueOf(tid)}, null);
                        assert c != null;
                        if (c.moveToFirst()) {
                            final String rid = c.getString(0);
                            Log.d(TAG, "recipient_ids: ", rid);
                            final Contact con = new Contact(Utils.parseLong(rid, -1));
                            con.update(context, true, false);
                            n = DataProvider.Logs.cleanNumber(con.getNumber(), false);
                            THREAD_TO_NUMBER.put(tid, n);
                        }
                        c.close();
                    }
                    if (n != null) {
                        cv.put(DataProvider.Logs.REMOTE, n);
                    }
                }

                cv.put(DataProvider.Logs.PLAN_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.RULE_ID, DataProvider.NO_ID);
                cv.put(DataProvider.Logs.TYPE, DataProvider.TYPE_MMS);
                cv.put(DataProvider.Logs.DATE, fixDate(d));
                cv.put(DataProvider.Logs.AMOUNT, 1);
                if (roaming) {
                    cv.put(DataProvider.Logs.ROAMED, 1);
                }
                if (idSimId >= 0) {
                    cv.put(DataProvider.Logs.MYNUMBER, cursor.getString(idSimId));
                } else if (!TextUtils.isEmpty(mynumber)) {
                    cv.put(DataProvider.Logs.MYNUMBER, mynumber);
                }
                cvalues.add(cv);
                if (cvalues.size() >= CallMeter.HUNDRET) {
                    cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                            cvalues.toArray(new ContentValues[cvalues.size()]));
                    Log.d(TAG, "new mms: ", cvalues.size());
                    cvalues.clear();
                }
            } while (cursor.moveToNext());
            if (cvalues.size() > 0) {
                cr.bulkInsert(DataProvider.Logs.CONTENT_URI,
                        cvalues.toArray(new ContentValues[cvalues.size()]));
                Log.d(TAG, "new mms: ", cvalues.size());
            }
        }
        cursor.close();
        Log.d(TAG, "updateMMS(): done");
    }

    /**
     * Delete old logs to make this app fast.
     *
     * @param cr {@link ContentResolver}
     */
    private static void deleteOldLogs(final ContentResolver cr) {
        Log.d(TAG, "delete old logs: date < ", deleteBefore);
        try {
            final int ret = cr.delete(DataProvider.Logs.CONTENT_URI, DataProvider.Logs.DATE
                    + " < ?", new String[]{String.valueOf(deleteBefore)});
            Log.i(TAG, "deleted old logs from internal database: " + ret);
        } catch (IllegalStateException e) {
            Log.e(TAG, "WTF?", e);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        handler = new Handler() {
            @Override
            public void handleMessage(final Message msg) {
                Log.i(TAG, "In handleMessage...");
            }
        };
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        inUpdate = false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onHandleIntent(final Intent intent) {
        if (intent == null) {
            Log.w(TAG, "handleIntent(null)");
            return;
        }
        inUpdate = true;
        handleIntent(intent);
        inUpdate = false;
    }

    private void handleIntent(final Intent intent) {
        assert intent != null;
        long start = System.currentTimeMillis();
        final String a = intent.getAction();
        Log.d(TAG, "handleIntent(action=", a, ")");

        if (BuildConfig.DEBUG_LOG) {
            Log.i(TAG, "check call sim_id");
            checkCallsSimIdColumn(getContentResolver());
            Log.i(TAG, "check sms sim_id");
            checkSmsSimIdColumn(getContentResolver());
        }

        final WakeLock wakelock = acquire(a);

        final Handler h = Plans.getHandler();
        if (h != null) {
            h.sendEmptyMessage(Plans.MSG_BACKGROUND_START_MATCHER);
        }

        SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this);
        dateStart = p.getLong(Preferences.PREFS_DATE_BEGIN, DatePreference.DEFAULT_VALUE);
        deleteBefore = Preferences.getDeleteLogsBefore(p);
        splitAt160 = p.getBoolean(Preferences.PREFS_SPLIT_SMS_AT_160, false);
        final boolean showCallInfo = p.getBoolean(Preferences.PREFS_SHOWCALLINFO, false);
        final boolean askForPlan = p.getBoolean(Preferences.PREFS_ASK_FOR_PLAN, false);
        final String delimiter = p.getString(Preferences.PREFS_DELIMITER, " | ");

        final boolean runMatcher = a != null && a.equals(ACTION_RUN_MATCHER);
        boolean shortRun = runMatcher
                || a != null
                && (a.equals(ACTION_SHORT_RUN) || a.equals(ConnectivityManager.CONNECTIVITY_ACTION)
                || a.equals(Intent.ACTION_BOOT_COMPLETED)
                || a.equals(Intent.ACTION_SHUTDOWN) || a.equals(Intent.ACTION_REBOOT) || a
                .equals(Intent.ACTION_DATE_CHANGED));

        Log.d(TAG, "runMatcher: ", runMatcher);
        Log.d(TAG, "shortRun: ", shortRun);

        final ContentResolver cr = getContentResolver();
        boolean showDialog = false;
        if (!shortRun && h != null) {
            final Cursor c = cr.query(DataProvider.Logs.CONTENT_URI,
                    new String[]{DataProvider.Logs.PLAN_ID}, DataProvider.Logs.RULE_ID + " != "
                            + DataProvider.NO_ID + " AND " + DataProvider.Logs.TYPE + " != "
                            + DataProvider.TYPE_DATA, null, null
            );
            assert c != null;
            if (c.getCount() < UNMATHCEDLOGS_TO_SHOW_DIALOG) {
                showDialog = true;
                // skip if no plan is set up
                Cursor c1 = cr.query(DataProvider.Plans.CONTENT_URI,
                        new String[]{DataProvider.Plans.ID}, null, null, null);
                assert c1 != null;
                if (c1.getCount() <= 0) {
                    shortRun = true;
                    showDialog = false;
                }
                c1.close();
                // skip if no rule is set up
                c1 = cr.query(DataProvider.Rules.CONTENT_URI,
                        new String[]{DataProvider.Rules.ID}, null, null, null);
                assert c1 != null;
                if (c1.getCount() <= 0) {
                    shortRun = true;
                    showDialog = false;
                }
                c1.close();
                if (showDialog) {
                    h.sendEmptyMessage(Plans.MSG_BACKGROUND_START_RUNNER);
                }
            }
            c.close();
        }

        updateData(this, shortRun && !runMatcher);
        if (!shortRun || runMatcher) {
            if (deleteBefore > 0L) {
                deleteOldLogs(cr);
            }
            updateCalls(this);
            updateSMS(cr, DataProvider.DIRECTION_IN);
            updateSMS(cr, DataProvider.DIRECTION_OUT);
            updateMMS(this);
            if (RuleMatcher.match(this, showDialog)) {
                StatsAppWidgetProvider.updateWidgets(this);
                LogsAppWidgetProvider.updateWidgets(this);
            }
        } else if (roaming) {
            updateCalls(this);
            updateSMS(cr, DataProvider.DIRECTION_IN);
            updateSMS(cr, DataProvider.DIRECTION_OUT);
            updateMMS(this);
        }

        if (showDialog) {
            h.sendEmptyMessage(Plans.MSG_BACKGROUND_STOP_RUNNER);
        }

        if ((showCallInfo || askForPlan) && a != null
                && a.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
            final Cursor c = cr.query(DataProvider.Logs.CONTENT_URI, DataProvider.Logs.PROJECTION,
                    DataProvider.Logs.TYPE + " = " + DataProvider.TYPE_CALL, null,
                    DataProvider.Logs.DATE + " DESC");
            if (c != null && c.moveToFirst()) {
                final long id = c.getLong(DataProvider.Logs.INDEX_ID);
                final long date = c.getLong(DataProvider.Logs.INDEX_DATE);
                final long amount = c.getLong(DataProvider.Logs.INDEX_AMOUNT);

                final long now = System.currentTimeMillis();
                if (amount > 0L && date + amount * CallMeter.MILLIS + GAP_FOR_LOGS >= now) {
                    // only show real calls
                    // only show calls made just now
                    final float cost = c.getFloat(DataProvider.Logs.INDEX_COST);
                    final String planname = DataProvider.Plans.getName(cr,
                            c.getLong(DataProvider.Logs.INDEX_PLAN_ID));
                    StringBuilder sb = new StringBuilder();
                    sb.append(Common.prettySeconds(amount, false));
                    if (cost > 0) {
                        String currencyFormat = Preferences.getCurrencyFormat(this);
                        sb.append(delimiter).append(String.format(currencyFormat, cost));
                    }
                    if (planname != null) {
                        sb.insert(0, planname + ": ");
                    } else if (askForPlan) {
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                Log.i(TAG, "launching ask for plan dialog");
                                final Intent i = new Intent(LogRunnerService.this,
                                        AskForPlan.class);
                                i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                i.putExtra(AskForPlan.EXTRA_ID, id);
                                i.putExtra(AskForPlan.EXTRA_DATE, date);
                                i.putExtra(AskForPlan.EXTRA_AMOUNT, amount);
                                LogRunnerService.this.startActivity(i);
                            }
                        });
                    }
                    if (showCallInfo) {
                        final String s = sb.toString();
                        Log.i(TAG, "Toast: " + s);
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                final Toast toast = Toast.makeText(LogRunnerService.this, s,
                                        Toast.LENGTH_LONG);
                                toast.show();
                            }
                        });
                    }
                } else {
                    Log.i(TAG, "skip Toast: amount=" + amount);
                    Log.i(TAG, "skip Toast: date+amount+gap="
                            + (date + amount * CallMeter.MILLIS + GAP_FOR_LOGS));
                    Log.i(TAG, "skip Toast: now            =" + now);
                }
            }
            if (c != null && !c.isClosed()) {
                c.close();
            }
        }

        release(wakelock, h, a);
        long end = System.currentTimeMillis();
        Log.i(TAG, "onHandleIntent(", a, "): ", end - start, "ms");
    }

    /**
     * Acquire {@link WakeLock} and init service.
     *
     * @param a action
     * @return {@link WakeLock}
     */
    private WakeLock acquire(final String a) {
        final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        final PowerManager.WakeLock wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        wakelock.acquire();
        Log.i(TAG, "got wakelock");

        if (a != null
                && (a.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED) || a
                .equals(ACTION_SMS))) {
            Log.i(TAG, "sleep for " + WAIT_FOR_LOGS + "ms");
            try {
                Thread.sleep(WAIT_FOR_LOGS);
            } catch (InterruptedException e) {
                Log.e(TAG, "interrupted while waiting for logs", e);
            }
        }

        // update roaming info
        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        roaming = tm.isNetworkRoaming();
        Log.d(TAG, "roaming: ", roaming);
        mynumber = tm.getLine1Number();
        Log.d(TAG, "my number: ", mynumber);

        return wakelock;
    }

    /**
     * Release {@link WakeLock} and notify {@link Handler}.
     *
     * @param wakelock {@link WakeLock}
     * @param h        {@link Handler}
     * @param a        action
     */
    private void release(final WakeLock wakelock, final Handler h, final String a) {
        // schedule next update
        if (a == null || !a.equals(ACTION_SHORT_RUN)) {
            LogRunnerReceiver.schedNext(this, null);
        } else {
            LogRunnerReceiver.schedNext(this, a);
        }
        if (h != null) {
            h.sendEmptyMessage(Plans.MSG_BACKGROUND_STOP_MATCHER);
        }
        wakelock.release();
        Log.i(TAG, "wakelock released");
    }
}




Java Source Code List

android.preference.DatePreference.java
com.actionbarsherlock.BuildConfig.java
de.ub0r.android.callmeter.Ads.java
de.ub0r.android.callmeter.CallMeter.java
de.ub0r.android.callmeter.TrackingUtils.java
de.ub0r.android.callmeter.data.DataProvider.java
de.ub0r.android.callmeter.data.Device.java
de.ub0r.android.callmeter.data.ExportProvider.java
de.ub0r.android.callmeter.data.LogRunnerReceiver.java
de.ub0r.android.callmeter.data.LogRunnerService.java
de.ub0r.android.callmeter.data.NameCache.java
de.ub0r.android.callmeter.data.NameLoader.java
de.ub0r.android.callmeter.data.RuleMatcher.java
de.ub0r.android.callmeter.data.SysClassNet.java
de.ub0r.android.callmeter.ui.AboutActivity.java
de.ub0r.android.callmeter.ui.AddLogActivity.java
de.ub0r.android.callmeter.ui.AskForPlan.java
de.ub0r.android.callmeter.ui.Common.java
de.ub0r.android.callmeter.ui.HelpActivity.java
de.ub0r.android.callmeter.ui.IntroActivity.java
de.ub0r.android.callmeter.ui.LogsFragment.java
de.ub0r.android.callmeter.ui.PlansFragment.java
de.ub0r.android.callmeter.ui.Plans.java
de.ub0r.android.callmeter.ui.TrackingActivity.java
de.ub0r.android.callmeter.ui.TrackingSherlockActivity.java
de.ub0r.android.callmeter.ui.TrackingSherlockFragmentActivity.java
de.ub0r.android.callmeter.ui.TrackingSherlockPreferenceActivity.java
de.ub0r.android.callmeter.ui.prefs.BillModeListPreference.java
de.ub0r.android.callmeter.ui.prefs.CV2EditTextPreference.java
de.ub0r.android.callmeter.ui.prefs.CVBillModePreference.java
de.ub0r.android.callmeter.ui.prefs.CVCheckBoxPreference.java
de.ub0r.android.callmeter.ui.prefs.CVDatePreference.java
de.ub0r.android.callmeter.ui.prefs.CVEditTextPreference.java
de.ub0r.android.callmeter.ui.prefs.CVListPreference.java
de.ub0r.android.callmeter.ui.prefs.HourGroupEdit.java
de.ub0r.android.callmeter.ui.prefs.HourGroups.java
de.ub0r.android.callmeter.ui.prefs.NumberGroupEdit.java
de.ub0r.android.callmeter.ui.prefs.NumberGroups.java
de.ub0r.android.callmeter.ui.prefs.PlanEdit.java
de.ub0r.android.callmeter.ui.prefs.Plans.java
de.ub0r.android.callmeter.ui.prefs.PreferencesImport.java
de.ub0r.android.callmeter.ui.prefs.PreferencesPlain.java
de.ub0r.android.callmeter.ui.prefs.PreferencesRules.java
de.ub0r.android.callmeter.ui.prefs.Preferences.java
de.ub0r.android.callmeter.ui.prefs.RuleEdit.java
de.ub0r.android.callmeter.ui.prefs.Rules.java
de.ub0r.android.callmeter.ui.prefs.SimplePreferencesChild.java
de.ub0r.android.callmeter.ui.prefs.SimplePreferences.java
de.ub0r.android.callmeter.ui.prefs.UpDownPreference.java
de.ub0r.android.callmeter.ui.prefs.UpdateListener.java
de.ub0r.android.callmeter.widget.LogsAppWidgetConfigure.java
de.ub0r.android.callmeter.widget.LogsAppWidgetProvider.java
de.ub0r.android.callmeter.widget.StatsAppWidgetConfigure.java
de.ub0r.android.callmeter.widget.StatsAppWidgetProvider.java
yuku.ambilwarna.AmbilWarnaDialog.java
yuku.ambilwarna.AmbilWarnaKotak.java