org.mariotaku.twidere.provider.TwidereDataProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.mariotaku.twidere.provider.TwidereDataProvider.java

Source

/*
 *            Twidere - Twitter client for Android
 * 
 * Copyright (C) 2012 Mariotaku Lee <mariotaku.lee@gmail.com>
 *
 * 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 org.mariotaku.twidere.provider;

import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.Utils.clearAccountColor;
import static org.mariotaku.twidere.util.Utils.clearAccountName;
import static org.mariotaku.twidere.util.Utils.getAccountName;
import static org.mariotaku.twidere.util.Utils.getAccountScreenName;
import static org.mariotaku.twidere.util.Utils.getAllStatusesCount;
import static org.mariotaku.twidere.util.Utils.getBiggerTwitterProfileImage;
import static org.mariotaku.twidere.util.Utils.getTableId;
import static org.mariotaku.twidere.util.Utils.getTableNameById;
import static org.mariotaku.twidere.util.Utils.isFiltered;
import static org.mariotaku.twidere.util.Utils.notifyForUpdatedUri;
import static org.mariotaku.twidere.util.Utils.parseInt;

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;

import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.HomeActivity;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.BundleCursor;
import org.mariotaku.twidere.model.ParcelableDirectMessage;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.provider.TweetStore.Accounts;
import org.mariotaku.twidere.provider.TweetStore.DirectMessages;
import org.mariotaku.twidere.provider.TweetStore.DirectMessages.Conversation;
import org.mariotaku.twidere.provider.TweetStore.DirectMessages.ConversationsEntry;
import org.mariotaku.twidere.provider.TweetStore.Mentions;
import org.mariotaku.twidere.provider.TweetStore.Statuses;
import org.mariotaku.twidere.util.ArrayUtils;
import org.mariotaku.twidere.util.LazyImageLoader;
import org.mariotaku.twidere.util.NoDuplicatesArrayList;
import org.mariotaku.twidere.util.PermissionsManager;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.text.Html;

import com.twitter.Extractor;

public final class TwidereDataProvider extends ContentProvider implements Constants {

    private Context mContext;

    private SQLiteDatabase mDatabase;
    private PermissionsManager mPermissionsManager;
    private NotificationManager mNotificationManager;
    private SharedPreferences mPreferences;
    private LazyImageLoader mProfileImageLoader;

    private int mNewStatusesCount;
    private final List<ParcelableStatus> mNewMentions = new ArrayList<ParcelableStatus>();
    private final List<String> mNewMentionScreenNames = new NoDuplicatesArrayList<String>();
    private final List<Long> mNewMentionAccounts = new NoDuplicatesArrayList<Long>();
    private final List<ParcelableDirectMessage> mNewMessages = new ArrayList<ParcelableDirectMessage>();
    private final List<String> mNewMessageScreenNames = new NoDuplicatesArrayList<String>();
    private final List<Long> mNewMessageAccounts = new NoDuplicatesArrayList<Long>();

    private boolean mNotificationIsAudible;

    private final BroadcastReceiver mHomeActivityStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(final Context context, final Intent intent) {
            final String action = intent.getAction();
            if (BROADCAST_HOME_ACTIVITY_ONSTART.equals(action)) {
                mNotificationIsAudible = false;
            } else if (BROADCAST_HOME_ACTIVITY_ONSTOP.equals(action)) {
                mNotificationIsAudible = true;
            }
        }

    };

    @Override
    public int bulkInsert(final Uri uri, final ContentValues[] values) {
        try {
            final int table_id = getTableId(uri);
            final String table = getTableNameById(table_id);
            checkWritePermission(table_id, table);
            switch (table_id) {
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
            case TABLE_ID_DIRECT_MESSAGES:
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY:
                return 0;
            }
            int result = 0;
            if (table != null && values != null) {
                final int old_count;
                switch (table_id) {
                case TABLE_ID_STATUSES: {
                    old_count = getAllStatusesCount(mContext, Statuses.CONTENT_URI);
                    break;
                }
                case TABLE_ID_MENTIONS: {
                    old_count = getAllStatusesCount(mContext, Mentions.CONTENT_URI);
                    break;
                }
                default:
                    old_count = 0;
                }
                mDatabase.beginTransaction();
                for (final ContentValues contentValues : values) {
                    mDatabase.insert(table, null, contentValues);
                    result++;
                }
                mDatabase.setTransactionSuccessful();
                mDatabase.endTransaction();
                if (!"false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) {
                    switch (table_id) {
                    case TABLE_ID_STATUSES: {
                        mNewStatusesCount += getAllStatusesCount(mContext, Statuses.CONTENT_URI) - old_count;
                        break;
                    }
                    }
                }
            }
            if (result > 0) {
                onDatabaseUpdated(uri);
            }
            onNewItemsInserted(uri, values);
            return result;
        } catch (final SQLException e) {
            throw new IllegalStateException(e);
        }
    };

    @Override
    public int delete(final Uri uri, final String selection, final String[] selectionArgs) {
        try {
            final int table_id = getTableId(uri);
            final String table = getTableNameById(table_id);
            checkWritePermission(table_id, table);
            if (table_id == VIRTUAL_TABLE_ID_NOTIFICATIONS) {
                final List<String> segments = uri.getPathSegments();
                if (segments.size() != 2)
                    return 0;
                clearNotification(parseInt(segments.get(1)));
            }
            switch (table_id) {
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
            case TABLE_ID_DIRECT_MESSAGES:
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY:
                return 0;
            }
            if (table == null)
                return 0;
            final int result = mDatabase.delete(table, selection, selectionArgs);
            if (result > 0) {
                onDatabaseUpdated(uri);
            }
            return result;
        } catch (final SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public String getType(final Uri uri) {
        return null;
    }

    @Override
    public Uri insert(final Uri uri, final ContentValues values) {
        try {
            final int table_id = getTableId(uri);
            final String table = getTableNameById(table_id);
            checkWritePermission(table_id, table);
            switch (table_id) {
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
            case TABLE_ID_DIRECT_MESSAGES:
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY:
                return null;
            }
            if (table == null)
                return null;
            final long row_id = mDatabase.insert(table, null, values);
            if (!"false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY))) {
                switch (getTableId(uri)) {
                case TABLE_ID_STATUSES: {
                    mNewStatusesCount++;
                    break;
                }
                default:
                }
            }
            onDatabaseUpdated(uri);
            onNewItemsInserted(uri, values);
            return Uri.withAppendedPath(uri, String.valueOf(row_id));
        } catch (final SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        final TwidereApplication app = TwidereApplication.getInstance(mContext);
        mDatabase = app.getSQLiteDatabase();
        mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        mPreferences = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
        mProfileImageLoader = app.getProfileImageLoader();
        mPermissionsManager = new PermissionsManager(mContext);
        final IntentFilter filter = new IntentFilter();
        filter.addAction(BROADCAST_HOME_ACTIVITY_ONSTART);
        filter.addAction(BROADCAST_HOME_ACTIVITY_ONSTOP);
        mContext.registerReceiver(mHomeActivityStateReceiver, filter);
        return mDatabase != null;
    }

    @Override
    public Cursor query(final Uri uri, final String[] projection, final String selection,
            final String[] selectionArgs, final String sortOrder) {
        try {
            final int table_id = getTableId(uri);
            if (table_id == VIRTUAL_TABLE_ID_PERMISSIONS) {
                final Bundle bundle = new Bundle();
                bundle.putInt(INTENT_KEY_PERMISSIONS, mPermissionsManager.getPermissions(Binder.getCallingUid()));
                return new BundleCursor(bundle);
            }
            final String table = getTableNameById(table_id);
            checkReadPermission(table_id, table, projection);
            switch (table_id) {
            case VIRTUAL_TABLE_ID_CONSUMER_KEY_SECRET: {
                final String consumer_key = mPreferences.getString(PREFERENCE_KEY_CONSUMER_KEY,
                        TWITTER_CONSUMER_KEY);
                final String consumer_secret = mPreferences.getString(PREFERENCE_KEY_CONSUMER_SECRET,
                        TWITTER_CONSUMER_SECRET);
                final Bundle bundle = new Bundle();
                bundle.putString(PREFERENCE_KEY_CONSUMER_KEY,
                        isEmpty(consumer_key) ? TWITTER_CONSUMER_KEY : consumer_key);
                bundle.putString(PREFERENCE_KEY_CONSUMER_SECRET,
                        isEmpty(consumer_secret) ? TWITTER_CONSUMER_KEY : consumer_secret);
                return new BundleCursor(bundle);
            }
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATION: {
                final List<String> segments = uri.getPathSegments();
                if (segments.size() != 3)
                    return null;
                final String query = Conversation.QueryBuilder.buildByConversationId(projection,
                        Long.parseLong(segments.get(1)), Long.parseLong(segments.get(2)), selection, sortOrder);
                return mDatabase.rawQuery(query, selectionArgs);
            }
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME: {
                final List<String> segments = uri.getPathSegments();
                if (segments.size() != 3)
                    return null;
                final String query = Conversation.QueryBuilder.buildByScreenName(projection,
                        Long.parseLong(segments.get(1)), segments.get(2), selection, sortOrder);
                return mDatabase.rawQuery(query, selectionArgs);
            }
            case TABLE_ID_DIRECT_MESSAGES: {
                final String query = DirectMessages.QueryBuilder.build(projection, selection, sortOrder);
                return mDatabase.rawQuery(query, selectionArgs);
            }
            case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY: {
                return mDatabase.rawQuery(ConversationsEntry.QueryBuilder.build(selection), null);
            }
            }
            if (table == null)
                return null;
            return mDatabase.query(table, projection, selection, selectionArgs, null, null, sortOrder);
        } catch (final SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public int update(final Uri uri, final ContentValues values, final String selection,
            final String[] selectionArgs) {
        try {
            final int table_id = getTableId(uri);
            final String table = getTableNameById(table_id);
            int result = 0;
            if (table != null) {
                switch (table_id) {
                case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
                case TABLE_ID_DIRECT_MESSAGES:
                case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY:
                    return 0;
                }
                result = mDatabase.update(table, values, selection, selectionArgs);
            }
            if (result > 0) {
                onDatabaseUpdated(uri);
            }
            return result;
        } catch (final SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    private void buildNotification(final NotificationCompat.Builder builder, final String ticker,
            final String title, final String message, final int icon, final Bitmap large_icon,
            final Intent content_intent, final Intent delete_intent) {
        final Context context = getContext();
        builder.setTicker(ticker);
        builder.setContentTitle(title);
        builder.setContentText(message);
        builder.setAutoCancel(true);
        builder.setWhen(System.currentTimeMillis());
        builder.setSmallIcon(icon);
        if (large_icon != null) {
            builder.setLargeIcon(large_icon);
        }
        if (delete_intent != null) {
            builder.setDeleteIntent(
                    PendingIntent.getBroadcast(context, 0, delete_intent, PendingIntent.FLAG_UPDATE_CURRENT));
        }
        if (content_intent != null) {
            builder.setContentIntent(
                    PendingIntent.getActivity(context, 0, content_intent, PendingIntent.FLAG_UPDATE_CURRENT));
        }
        int defaults = 0;
        final Calendar now = Calendar.getInstance();
        if (mNotificationIsAudible
                && !mPreferences.getBoolean("slient_notifications_at_" + now.get(Calendar.HOUR_OF_DAY), false)) {
            if (mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_HAVE_SOUND, false)) {
                builder.setSound(Uri.parse(mPreferences.getString(PREFERENCE_KEY_NOTIFICATION_RINGTONE,
                        Settings.System.DEFAULT_RINGTONE_URI.getPath())), Notification.STREAM_DEFAULT);
            }
            if (mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_HAVE_VIBRATION, false)) {
                defaults |= Notification.DEFAULT_VIBRATE;
            }
        }
        if (mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_HAVE_LIGHTS, false)) {
            final int color_def = context.getResources().getColor(R.color.holo_blue_dark);
            final int color = mPreferences.getInt(PREFERENCE_KEY_NOTIFICATION_LIGHT_COLOR, color_def);
            builder.setLights(color, 1000, 2000);
        }
        builder.setDefaults(defaults);
    }

    private boolean checkPermission(final int level) {
        return mPermissionsManager.checkCallingPermission(level);
    }

    private void checkReadPermission(final int id, final String table, final String[] projection) {
        switch (id) {
        case VIRTUAL_TABLE_ID_CONSUMER_KEY_SECRET: {
            if (!checkPermission(PERMISSION_ACCOUNTS))
                throw new SecurityException(
                        "Access database " + table + " requires level PERMISSION_LEVEL_ACCOUNTS");
            break;
        }
        case TABLE_ID_ACCOUNTS: {
            // Reading some infomation like user_id, screen_name etc is
            // okay, but reading columns like password requires higher
            // permission level.
            if (ArrayUtils.contains(projection, Accounts.BASIC_AUTH_PASSWORD, Accounts.OAUTH_TOKEN,
                    Accounts.TOKEN_SECRET) && !checkPermission(PERMISSION_ACCOUNTS))
                throw new SecurityException("Access column " + ArrayUtils.toString(projection, ',', true)
                        + " in database accounts requires level PERMISSION_LEVEL_ACCOUNTS");
            if (!checkPermission(PERMISSION_READ))
                throw new SecurityException("Access database " + table + " requires level PERMISSION_LEVEL_READ");
            break;
        }
        case TABLE_ID_DIRECT_MESSAGES:
        case TABLE_ID_DIRECT_MESSAGES_INBOX:
        case TABLE_ID_DIRECT_MESSAGES_OUTBOX:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY: {
            if (!checkPermission(PERMISSION_DIRECT_MESSAGES))
                throw new SecurityException(
                        "Access database " + table + " requires level PERMISSION_LEVEL_DIRECT_MESSAGES");
            break;
        }
        case TABLE_ID_STATUSES:
        case TABLE_ID_MENTIONS:
        case TABLE_ID_TABS:
        case TABLE_ID_DRAFTS:
        case TABLE_ID_CACHED_USERS:
        case TABLE_ID_FILTERED_USERS:
        case TABLE_ID_FILTERED_KEYWORDS:
        case TABLE_ID_FILTERED_SOURCES:
        case TABLE_ID_FILTERED_LINKS:
        case TABLE_ID_TRENDS_LOCAL:
        case TABLE_ID_CACHED_STATUSES:
        case TABLE_ID_CACHED_HASHTAGS: {
            if (!checkPermission(PERMISSION_READ))
                throw new SecurityException("Access database " + table + " requires level PERMISSION_LEVEL_READ");
            break;
        }
        }
    }

    private void checkWritePermission(final int id, final String table) {
        switch (id) {
        case TABLE_ID_ACCOUNTS: {
            // Writing to accounts database is not allowed for third-party
            // applications.
            if (!mPermissionsManager.checkSignature(Binder.getCallingUid()))
                throw new SecurityException(
                        "Writing to accounts database is not allowed for third-party applications");
            break;
        }
        case TABLE_ID_DIRECT_MESSAGES:
        case TABLE_ID_DIRECT_MESSAGES_INBOX:
        case TABLE_ID_DIRECT_MESSAGES_OUTBOX:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATION:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATION_SCREEN_NAME:
        case TABLE_ID_DIRECT_MESSAGES_CONVERSATIONS_ENTRY: {
            if (!checkPermission(PERMISSION_DIRECT_MESSAGES))
                throw new SecurityException(
                        "Access database " + table + " requires level PERMISSION_LEVEL_DIRECT_MESSAGES");
            break;
        }
        case TABLE_ID_STATUSES:
        case TABLE_ID_MENTIONS:
        case TABLE_ID_TABS:
        case TABLE_ID_DRAFTS:
        case TABLE_ID_CACHED_USERS:
        case TABLE_ID_FILTERED_USERS:
        case TABLE_ID_FILTERED_KEYWORDS:
        case TABLE_ID_FILTERED_SOURCES:
        case TABLE_ID_FILTERED_LINKS:
        case TABLE_ID_TRENDS_LOCAL:
        case TABLE_ID_CACHED_STATUSES:
        case TABLE_ID_CACHED_HASHTAGS: {
            if (!checkPermission(PERMISSION_WRITE))
                throw new SecurityException("Access database " + table + " requires level PERMISSION_LEVEL_WRITE");
            break;
        }
        }
    }

    private void clearNotification(final int id) {
        switch (id) {
        case NOTIFICATION_ID_HOME_TIMELINE: {
            mNewStatusesCount = 0;
            break;
        }
        case NOTIFICATION_ID_MENTIONS: {
            mNewMentionAccounts.clear();
            mNewMentions.clear();
            mNewMentionScreenNames.clear();
            break;
        }
        case NOTIFICATION_ID_DIRECT_MESSAGES: {
            mNewMessageAccounts.clear();
            mNewMessages.clear();
            mNewMessageScreenNames.clear();
            break;
        }
        }
        mNotificationManager.cancel(id);
    }

    private void displayMentionsNotification(final Context context, final ContentValues[] values) {
        final Resources res = context.getResources();
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        final boolean display_screen_name = NAME_DISPLAY_OPTION_SCREEN_NAME
                .equals(mPreferences.getString(PREFERENCE_KEY_NAME_DISPLAY_OPTION, NAME_DISPLAY_OPTION_BOTH));
        final boolean display_hires_profile_image = res.getBoolean(R.bool.hires_profile_image);
        final Intent delete_intent = new Intent(BROADCAST_NOTIFICATION_CLEARED);
        final Bundle delete_extras = new Bundle();
        delete_extras.putInt(INTENT_KEY_NOTIFICATION_ID, NOTIFICATION_ID_MENTIONS);
        delete_intent.putExtras(delete_extras);
        final Intent content_intent;
        int notified_count = 0;
        // Add statuses that not filtered to list for future use.
        for (final ContentValues value : values) {
            final ParcelableStatus status = new ParcelableStatus(value);
            if (!isFiltered(mDatabase, status)) {
                mNewMentions.add(status);
                mNewMentionScreenNames.add(status.screen_name);
                mNewMentionAccounts.add(status.account_id);
                notified_count++;
            }
        }
        Collections.sort(mNewMentions);
        final int mentions_size = mNewMentions.size();
        if (notified_count == 0 || mentions_size == 0 || mNewMentionScreenNames.size() == 0)
            return;
        final String title;
        if (mentions_size > 1) {
            builder.setNumber(mentions_size);
        }
        final int screen_names_size = mNewMentionScreenNames.size();
        final ParcelableStatus status = mNewMentions.get(0);
        if (mentions_size == 1) {
            final Uri.Builder uri_builder = new Uri.Builder();
            uri_builder.scheme(SCHEME_TWIDERE);
            uri_builder.authority(AUTHORITY_STATUS);
            uri_builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(status.account_id));
            uri_builder.appendQueryParameter(QUERY_PARAM_STATUS_ID, String.valueOf(status.status_id));
            content_intent = new Intent(Intent.ACTION_VIEW, uri_builder.build());
        } else {
            content_intent = new Intent(context, HomeActivity.class);
            content_intent.setAction(Intent.ACTION_MAIN);
            content_intent.addCategory(Intent.CATEGORY_LAUNCHER);
            final Bundle content_extras = new Bundle();
            content_extras.putInt(INTENT_KEY_INITIAL_TAB, HomeActivity.TAB_POSITION_MENTIONS);
            content_intent.putExtras(content_extras);
        }
        if (screen_names_size > 1) {
            title = res.getString(R.string.notification_mention_multiple,
                    display_screen_name ? "@" + status.screen_name : status.name, screen_names_size - 1);
        } else {
            title = res.getString(R.string.notification_mention,
                    display_screen_name ? "@" + status.screen_name : status.name);
        }
        final String profile_image_url_string = status.profile_image_url_string;
        final File profile_image_file = mProfileImageLoader.getCachedImageFile(
                display_hires_profile_image ? getBiggerTwitterProfileImage(profile_image_url_string)
                        : profile_image_url_string);
        final int w = res.getDimensionPixelSize(R.dimen.notification_large_icon_width);
        final int h = res.getDimensionPixelSize(R.dimen.notification_large_icon_height);
        final Bitmap profile_image = profile_image_file != null && profile_image_file.isFile()
                ? BitmapFactory.decodeFile(profile_image_file.getPath())
                : null;
        final Bitmap profile_image_fallback = BitmapFactory.decodeResource(res,
                R.drawable.ic_profile_image_default);
        builder.setLargeIcon(Bitmap
                .createScaledBitmap(profile_image != null ? profile_image : profile_image_fallback, w, h, true));
        buildNotification(builder, title, title, status.text_plain, R.drawable.ic_stat_mention, null,
                content_intent, delete_intent);
        if (mentions_size > 1) {
            final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder);
            final int max = Math.min(4, mentions_size);
            for (int i = 0; i < max; i++) {
                final ParcelableStatus s = mNewMentions.get(i);
                final String name = display_screen_name ? "@" + s.screen_name : s.name;
                style.addLine(Html.fromHtml("<b>" + name + "</b>: "
                        + stripMentionText(s.text_plain, getAccountScreenName(context, s.account_id))));
            }
            if (max == 4 && mentions_size - max > 0) {
                style.addLine(context.getString(R.string.and_more, mentions_size - max));
            }
            final StringBuilder summary = new StringBuilder();
            final int accounts_count = mNewMentionAccounts.size();
            if (accounts_count > 0) {
                for (int i = 0; i < accounts_count; i++) {
                    final String name = display_screen_name
                            ? "@" + getAccountScreenName(context, mNewMentionAccounts.get(i))
                            : getAccountName(context, mNewMentionAccounts.get(i));
                    summary.append(name);
                    if (i != accounts_count - 1) {
                        summary.append(", ");
                    }
                }
                style.setSummaryText(summary);
            }
            mNotificationManager.notify(NOTIFICATION_ID_MENTIONS, style.build());
        } else {
            final Intent reply_intent = new Intent(INTENT_ACTION_COMPOSE);
            final Bundle bundle = new Bundle();
            final List<String> mentions = new Extractor().extractMentionedScreennames(status.text_plain);
            mentions.remove(status.screen_name);
            mentions.add(0, status.screen_name);
            bundle.putInt(INTENT_KEY_NOTIFICATION_ID, NOTIFICATION_ID_MENTIONS);
            bundle.putStringArray(INTENT_KEY_MENTIONS, mentions.toArray(new String[mentions.size()]));
            bundle.putLong(INTENT_KEY_ACCOUNT_ID, status.account_id);
            bundle.putLong(INTENT_KEY_IN_REPLY_TO_ID, status.status_id);
            bundle.putString(INTENT_KEY_IN_REPLY_TO_SCREEN_NAME, status.screen_name);
            bundle.putString(INTENT_KEY_IN_REPLY_TO_NAME, status.name);
            reply_intent.putExtras(bundle);
            reply_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            builder.addAction(R.drawable.ic_menu_reply, context.getString(R.string.reply),
                    PendingIntent.getActivity(context, 0, reply_intent, PendingIntent.FLAG_UPDATE_CURRENT));
            final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
            style.bigText(stripMentionText(status.text_plain, getAccountScreenName(context, status.account_id)));
            mNotificationManager.notify(NOTIFICATION_ID_MENTIONS, style.build());
        }
    }

    private void displayMessagesNotification(final Context context, final ContentValues[] values) {
        final Resources res = context.getResources();
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        final boolean display_screen_name = NAME_DISPLAY_OPTION_SCREEN_NAME
                .equals(mPreferences.getString(PREFERENCE_KEY_NAME_DISPLAY_OPTION, NAME_DISPLAY_OPTION_BOTH));
        final boolean display_hires_profile_image = res.getBoolean(R.bool.hires_profile_image);
        final Intent delete_intent = new Intent(BROADCAST_NOTIFICATION_CLEARED);
        final Bundle delete_extras = new Bundle();
        delete_extras.putInt(INTENT_KEY_NOTIFICATION_ID, NOTIFICATION_ID_DIRECT_MESSAGES);
        delete_intent.putExtras(delete_extras);
        final Intent content_intent;
        int notified_count = 0;
        for (final ContentValues value : values) {
            final String screen_name = value.getAsString(DirectMessages.SENDER_SCREEN_NAME);
            final ParcelableDirectMessage message = new ParcelableDirectMessage(value);
            mNewMessages.add(message);
            mNewMessageScreenNames.add(screen_name);
            mNewMessageAccounts.add(message.account_id);
            notified_count++;
        }
        Collections.sort(mNewMessages);
        final int messages_size = mNewMessages.size();
        if (notified_count == 0 || messages_size == 0 || mNewMessageScreenNames.size() == 0)
            return;
        final String title;
        if (messages_size > 1) {
            builder.setNumber(messages_size);
        }
        final int screen_names_size = mNewMessageScreenNames.size();
        final ParcelableDirectMessage message = mNewMessages.get(0);
        if (messages_size == 1) {
            final Uri.Builder uri_builder = new Uri.Builder();
            final long account_id = message.account_id;
            final long conversation_id = message.sender_id;
            uri_builder.scheme(SCHEME_TWIDERE);
            uri_builder.authority(AUTHORITY_DIRECT_MESSAGES_CONVERSATION);
            uri_builder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(account_id));
            uri_builder.appendQueryParameter(QUERY_PARAM_CONVERSATION_ID, String.valueOf(conversation_id));
            content_intent = new Intent(Intent.ACTION_VIEW, uri_builder.build());
        } else {
            content_intent = new Intent(context, HomeActivity.class);
            content_intent.setAction(Intent.ACTION_MAIN);
            content_intent.addCategory(Intent.CATEGORY_LAUNCHER);
            final Bundle content_extras = new Bundle();
            content_extras.putInt(INTENT_KEY_INITIAL_TAB, HomeActivity.TAB_POSITION_MESSAGES);
            content_intent.putExtras(content_extras);
        }
        if (screen_names_size > 1) {
            title = res.getString(R.string.notification_direct_message_multiple,
                    display_screen_name ? "@" + message.sender_screen_name : message.sender_name,
                    screen_names_size - 1);
        } else {
            title = res.getString(R.string.notification_direct_message,
                    display_screen_name ? "@" + message.sender_screen_name : message.sender_name);
        }
        final String text_plain = message.text_plain;
        final String profile_image_url_string = message.sender_profile_image_url_string;
        final File profile_image_file = mProfileImageLoader.getCachedImageFile(
                display_hires_profile_image ? getBiggerTwitterProfileImage(profile_image_url_string)
                        : profile_image_url_string);
        final int w = res.getDimensionPixelSize(R.dimen.notification_large_icon_width);
        final int h = res.getDimensionPixelSize(R.dimen.notification_large_icon_height);
        final Bitmap profile_image = profile_image_file != null && profile_image_file.isFile()
                ? BitmapFactory.decodeFile(profile_image_file.getPath())
                : null;
        final Bitmap profile_image_fallback = BitmapFactory.decodeResource(res,
                R.drawable.ic_profile_image_default);
        builder.setLargeIcon(Bitmap
                .createScaledBitmap(profile_image != null ? profile_image : profile_image_fallback, w, h, true));
        buildNotification(builder, title, title, text_plain, R.drawable.ic_stat_direct_message, null,
                content_intent, delete_intent);
        final StringBuilder summary = new StringBuilder();
        final int accounts_count = mNewMessageAccounts.size();
        if (accounts_count > 0) {
            for (int i = 0; i < accounts_count; i++) {
                final String name = display_screen_name
                        ? "@" + getAccountScreenName(context, mNewMessageAccounts.get(i))
                        : getAccountName(context, mNewMessageAccounts.get(i));
                summary.append(name);
                if (i != accounts_count - 1) {
                    summary.append(", ");
                }
            }
        }
        if (messages_size > 1) {
            final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder);
            final int max = Math.min(4, messages_size);
            for (int i = 0; i < max; i++) {
                final ParcelableDirectMessage s = mNewMessages.get(i);
                final String name = display_screen_name ? "@" + s.sender_screen_name : s.sender_name;
                style.addLine(Html.fromHtml("<b>" + name + "</b>: " + s.text_plain));
            }
            if (max == 4 && messages_size - max > 0) {
                style.addLine(context.getString(R.string.and_more, messages_size - max));
            }
            style.setSummaryText(summary);
            mNotificationManager.notify(NOTIFICATION_ID_DIRECT_MESSAGES, style.build());
        } else {
            final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle(builder);
            style.bigText(message.text_plain);
            style.setSummaryText(summary);
            mNotificationManager.notify(NOTIFICATION_ID_DIRECT_MESSAGES, style.build());
        }
    }

    private void onDatabaseUpdated(final Uri uri) {
        if (uri == null)
            return;
        if ("false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY)))
            return;
        final Context context = getContext();
        switch (getTableId(uri)) {
        case TABLE_ID_ACCOUNTS: {
            clearAccountColor();
            clearAccountName();
            context.sendBroadcast(new Intent(BROADCAST_ACCOUNT_LIST_DATABASE_UPDATED));
            break;
        }
        case TABLE_ID_DRAFTS: {
            context.sendBroadcast(new Intent(BROADCAST_DRAFTS_DATABASE_UPDATED));
            break;
        }
        case TABLE_ID_STATUSES:
        case TABLE_ID_MENTIONS:
        case TABLE_ID_DIRECT_MESSAGES_INBOX:
        case TABLE_ID_DIRECT_MESSAGES_OUTBOX: {
            notifyForUpdatedUri(context, uri);
            break;
        }
        case TABLE_ID_TRENDS_LOCAL: {
            context.sendBroadcast(new Intent(BROADCAST_TRENDS_UPDATED));
            break;
        }
        case TABLE_ID_TABS: {
            context.sendBroadcast(new Intent(BROADCAST_TABS_UPDATED));
            break;
        }
        case TABLE_ID_FILTERED_LINKS:
        case TABLE_ID_FILTERED_USERS:
        case TABLE_ID_FILTERED_KEYWORDS:
        case TABLE_ID_FILTERED_SOURCES: {
            context.sendBroadcast(new Intent(BROADCAST_FILTERS_UPDATED));
            break;
        }
        default:
            return;
        }
        context.sendBroadcast(new Intent(BROADCAST_DATABASE_UPDATED));
    }

    private void onNewItemsInserted(final Uri uri, final ContentValues... values) {
        if (uri == null || values == null || values.length == 0)
            return;
        if ("false".equals(uri.getQueryParameter(QUERY_PARAM_NOTIFY)))
            return;
        final Context context = getContext();
        final Resources res = context.getResources();
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        switch (getTableId(uri)) {
        case TABLE_ID_STATUSES: {
            if (!mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_ENABLE_HOME_TIMELINE, false))
                return;
            final String message = res.getQuantityString(R.plurals.Ntweets, mNewStatusesCount, mNewStatusesCount);
            final Intent delete_intent = new Intent(BROADCAST_NOTIFICATION_CLEARED);
            final Bundle delete_extras = new Bundle();
            delete_extras.putInt(INTENT_KEY_NOTIFICATION_ID, NOTIFICATION_ID_HOME_TIMELINE);
            delete_intent.putExtras(delete_extras);
            final Intent content_intent = new Intent(context, HomeActivity.class);
            content_intent.setAction(Intent.ACTION_MAIN);
            content_intent.addCategory(Intent.CATEGORY_LAUNCHER);
            final Bundle content_extras = new Bundle();
            content_extras.putInt(INTENT_KEY_INITIAL_TAB, HomeActivity.TAB_POSITION_HOME);
            content_intent.putExtras(content_extras);
            builder.setOnlyAlertOnce(true);
            buildNotification(builder, res.getString(R.string.new_notifications), message, message,
                    R.drawable.ic_stat_tweet, null, content_intent, delete_intent);
            mNotificationManager.notify(NOTIFICATION_ID_HOME_TIMELINE, builder.build());
            break;
        }
        case TABLE_ID_MENTIONS: {
            if (mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_ENABLE_MENTIONS, false)) {
                displayMentionsNotification(context, values);
            }
            break;
        }
        case TABLE_ID_DIRECT_MESSAGES_INBOX: {
            if (mPreferences.getBoolean(PREFERENCE_KEY_NOTIFICATION_ENABLE_DIRECT_MESSAGES, false)) {
                displayMessagesNotification(context, values);
            }
            break;
        }
        }
    }

    private static String stripMentionText(final String text, final String my_screen_name) {
        if (text == null || my_screen_name == null)
            return text;
        final String temp = "@" + my_screen_name + " ";
        if (text.startsWith(temp))
            return text.substring(temp.length());
        return text;
    }
}