Android Open Source - DashClockWidget Sms Extension






From Project

Back to project page DashClockWidget.

License

The source code is released under:

Apache License

If you think the Android project DashClockWidget 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 2013 Google Inc.//from ww  w  .  jav  a 2  s .  c  om
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.apps.dashclock.phone;

import com.google.android.apps.dashclock.LogUtils;
import com.google.android.apps.dashclock.api.DashClockExtension;
import com.google.android.apps.dashclock.api.ExtensionData;

import net.nurik.roman.dashclock.R;

import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.provider.Telephony;
import android.text.TextUtils;

import java.util.HashSet;
import java.util.Set;

import static com.google.android.apps.dashclock.LogUtils.LOGD;
import static com.google.android.apps.dashclock.LogUtils.LOGE;
import static com.google.android.apps.dashclock.LogUtils.LOGW;

/**
 * Unread SMS and MMS's extension.
 */
public class SmsExtension extends DashClockExtension {
    private static final String TAG = LogUtils.makeLogTag(SmsExtension.class);

    @Override
    protected void onInitialize(boolean isReconnect) {
        super.onInitialize(isReconnect);
        if (!isReconnect) {
            addWatchContentUris(new String[]{
                    TelephonyProviderConstants.MmsSms.CONTENT_URI.toString(),
            });
        }
    }

    @Override
    protected void onUpdateData(int reason) {
        long lastUnreadThreadId = 0;
        Set<Long> unreadThreadIds = new HashSet<Long>();
        Set<String> unreadThreadParticipantNames = new HashSet<String>();
        boolean showingAllConversationParticipants = false;

        Cursor cursor = tryOpenSimpleThreadsCursor();
        if (cursor != null) {
            while (cursor.moveToNext()) {
                if (cursor.getInt(SimpleThreadsQuery.READ) == 0) {
                    long threadId = cursor.getLong(SimpleThreadsQuery._ID);
                    unreadThreadIds.add(threadId);
                    lastUnreadThreadId = threadId;

                    // Some devices will fail on tryOpenMmsSmsCursor below, so
                    // store a list of participants on unread threads as a fallback.
                    String recipientIdsStr = cursor.getString(SimpleThreadsQuery.RECIPIENT_IDS);
                    if (!TextUtils.isEmpty(recipientIdsStr)) {
                        String[] recipientIds = TextUtils.split(recipientIdsStr, " ");
                        for (String recipientId : recipientIds) {
                            Cursor canonAddrCursor = tryOpenCanonicalAddressCursorById(
                                    Long.parseLong(recipientId));
                            if (canonAddrCursor == null) {
                                continue;
                            }
                            if (canonAddrCursor.moveToFirst()) {
                                String address = canonAddrCursor.getString(
                                        CanonicalAddressQuery.ADDRESS);
                                String displayName = getDisplayNameForContact(0, address);
                                if (!TextUtils.isEmpty(displayName)) {
                                    unreadThreadParticipantNames.add(displayName);
                                }
                            }
                            canonAddrCursor.close();
                        }
                    }
                }
            }
            cursor.close();

            LOGD(TAG, "Unread thread IDs: [" + TextUtils.join(", ", unreadThreadIds) + "]");
        }

        int unreadConversations = 0;
        StringBuilder names = new StringBuilder();
        cursor = tryOpenMmsSmsCursor();
        if (cursor != null) {
            // Most devices will hit this code path.
            while (cursor.moveToNext()) {
                // Get display name. SMS's are easy; MMS's not so much.
                long id = cursor.getLong(MmsSmsQuery._ID);
                long contactId = cursor.getLong(MmsSmsQuery.PERSON);
                String address = cursor.getString(MmsSmsQuery.ADDRESS);
                long threadId = cursor.getLong(MmsSmsQuery.THREAD_ID);
                if (unreadThreadIds != null && !unreadThreadIds.contains(threadId)) {
                    // We have the list of all thread IDs (same as what the messaging app uses), and
                    // this supposedly unread message's thread isn't in the list. This message is
                    // likely an orphaned message whose thread was deleted. Not skipping it is
                    // likely the cause of http://code.google.com/p/dashclock/issues/detail?id=8
                    LOGD(TAG, "Skipping probably orphaned message " + id + " with thread ID "
                            + threadId);
                    continue;
                }

                ++unreadConversations;
                lastUnreadThreadId = threadId;

                if (contactId == 0 && TextUtils.isEmpty(address) && id != 0) {
                    // Try MMS addr query
                    Cursor addrCursor = tryOpenMmsAddrCursor(id);
                    if (addrCursor != null) {
                        if (addrCursor.moveToFirst()) {
                            contactId = addrCursor.getLong(MmsAddrQuery.CONTACT_ID);
                            address = addrCursor.getString(MmsAddrQuery.ADDRESS);
                        }
                        addrCursor.close();
                    }
                }

                String displayName = getDisplayNameForContact(contactId, address);

                if (names.length() > 0) {
                    names.append(", ");
                }
                names.append(displayName);
            }
            cursor.close();

        } else {
            // In case the cursor is null (some Samsung devices like the Galaxy S4), use the
            // fall back on the list of participants in unread threads.
            unreadConversations = unreadThreadIds.size();
            names.append(TextUtils.join(", ", unreadThreadParticipantNames));
            showingAllConversationParticipants = true;
        }

        PackageManager pm = getPackageManager();
        Intent clickIntent = null;
        if (unreadConversations == 1 && lastUnreadThreadId > 0) {
            clickIntent = new Intent(Intent.ACTION_VIEW,
                    TelephonyProviderConstants.MmsSms.CONTENT_CONVERSATIONS_URI.buildUpon()
                            .appendPath(Long.toString(lastUnreadThreadId)).build());
        }

        // If the default SMS app doesn't support ACTION_VIEW on the conversation URI,
        // or if there are multiple unread conversations, try opening the app landing screen
        // by implicit intent.
        if (clickIntent == null || pm.resolveActivity(clickIntent, 0) == null) {
            clickIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
                    Intent.CATEGORY_APP_MESSAGING);

            // If the default SMS app doesn't support CATEGORY_APP_MESSAGING, try KitKat's
            // new API to get the default package (if the API is available).
            if (pm.resolveActivity(clickIntent, 0) == null) {
                clickIntent = tryGetKitKatDefaultSmsActivity();
            }
        }

        publishUpdate(new ExtensionData()
                .visible(unreadConversations > 0)
                .icon(R.drawable.ic_extension_sms)
                .status(Integer.toString(unreadConversations))
                .expandedTitle(
                        getResources().getQuantityString(
                                R.plurals.sms_title_template, unreadConversations,
                                unreadConversations))
                .expandedBody(getString(showingAllConversationParticipants
                        ? R.string.sms_body_all_participants_template
                        : R.string.sms_body_template,
                        names.toString()))
                .clickIntent(clickIntent));
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private Intent tryGetKitKatDefaultSmsActivity() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            String smsPackage = Telephony.Sms.getDefaultSmsPackage(this);
            if (TextUtils.isEmpty(smsPackage)) {
                return null;
            }

            return new Intent()
                    .setAction(Intent.ACTION_MAIN)
                    .addCategory(Intent.CATEGORY_LAUNCHER)
                    .setPackage(smsPackage);
        }
        return null;
    }

    /**
     * Returns the display name for the contact with the given ID and/or the given address
     * (phone number). One or both parameters should be provided.
     */
    private String getDisplayNameForContact(long contactId, String address) {
        String displayName = address;

        if (contactId > 0) {
            Cursor contactCursor = tryOpenContactsCursorById(contactId);
            if (contactCursor != null) {
                if (contactCursor.moveToFirst()) {
                    displayName = contactCursor.getString(RawContactsQuery.DISPLAY_NAME);
                } else {
                    contactId = 0;
                }
                contactCursor.close();
            }
        }

        if (contactId <= 0) {
            Cursor contactCursor = tryOpenContactsCursorByAddress(address);
            if (contactCursor != null) {
                if (contactCursor.moveToFirst()) {
                    displayName = contactCursor.getString(ContactsQuery.DISPLAY_NAME);
                }
                contactCursor.close();
            }
        }

        return displayName;
    }

    private Cursor tryOpenMmsSmsCursor() {
        try {
            return getContentResolver().query(
                    TelephonyProviderConstants.MmsSms.CONTENT_CONVERSATIONS_URI,
                    MmsSmsQuery.PROJECTION,
                    TelephonyProviderConstants.Mms.READ + "=0 AND "
                            + TelephonyProviderConstants.Mms.THREAD_ID + "!=0 AND ("
                            + TelephonyProviderConstants.Mms.MESSAGE_BOX + "="
                            + TelephonyProviderConstants.Mms.MESSAGE_BOX_INBOX + " OR "
                            + TelephonyProviderConstants.Sms.TYPE + "="
                            + TelephonyProviderConstants.Sms.MESSAGE_TYPE_INBOX + ")",
                    null,
                    null);

        } catch (Exception e) {
            // Catch all exceptions because the SMS provider is crashy
            // From developer console: "SQLiteException: table spam_filter already exists"
            LOGE(TAG, "Error accessing conversations cursor in SMS/MMS provider", e);
            return null;
        }
    }

    private Cursor tryOpenSimpleThreadsCursor() {
        try {
            return getContentResolver().query(
                    TelephonyProviderConstants.Threads.CONTENT_URI
                            .buildUpon()
                            .appendQueryParameter("simple", "true")
                            .build(),
                    SimpleThreadsQuery.PROJECTION,
                    null,
                    null,
                    null);

        } catch (Exception e) {
            LOGW(TAG, "Error accessing simple SMS threads cursor", e);
            return null;
        }
    }

    private Cursor tryOpenCanonicalAddressCursorById(long id) {
        try {
            return getContentResolver().query(
                    TelephonyProviderConstants.CanonicalAddresses.CONTENT_URI.buildUpon()
                            .build(),
                    CanonicalAddressQuery.PROJECTION,
                    TelephonyProviderConstants.CanonicalAddresses._ID + "=?",
                    new String[]{Long.toString(id)},
                    null);

        } catch (Exception e) {
            LOGE(TAG, "Error accessing canonical addresses cursor", e);
            return null;
        }
    }

    private Cursor tryOpenMmsAddrCursor(long mmsMsgId) {
        try {
            return getContentResolver().query(
                    TelephonyProviderConstants.Mms.CONTENT_URI.buildUpon()
                            .appendPath(Long.toString(mmsMsgId))
                            .appendPath("addr")
                            .build(),
                    MmsAddrQuery.PROJECTION,
                    TelephonyProviderConstants.Mms.Addr.MSG_ID + "=?",
                    new String[]{Long.toString(mmsMsgId)},
                    null);

        } catch (Exception e) {
            // Catch all exceptions because the SMS provider is crashy
            // From developer console: "SQLiteException: table spam_filter already exists"
            LOGE(TAG, "Error accessing MMS addresses cursor", e);
            return null;
        }
    }

    private Cursor tryOpenContactsCursorById(long contactId) {
        try {
            return getContentResolver().query(
                    ContactsContract.RawContacts.CONTENT_URI.buildUpon()
                            .appendPath(Long.toString(contactId))
                            .build(),
                    RawContactsQuery.PROJECTION,
                    null,
                    null,
                    null);

        } catch (Exception e) {
            LOGE(TAG, "Error accessing contacts provider", e);
            return null;
        }
    }

    private Cursor tryOpenContactsCursorByAddress(String phoneNumber) {
        try {
            return getContentResolver().query(
                    ContactsContract.PhoneLookup.CONTENT_FILTER_URI.buildUpon()
                            .appendPath(Uri.encode(phoneNumber)).build(),
                    ContactsQuery.PROJECTION,
                    null,
                    null,
                    null);

        } catch (Exception e) {
            // Can be called by the content provider (from Google Play crash/ANR console)
            // java.lang.IllegalArgumentException: URI: content://com.android.contacts/phone_lookup/
            LOGW(TAG, "Error looking up contact name", e);
            return null;
        }
    }

    private interface SimpleThreadsQuery {
        String[] PROJECTION = {
                TelephonyProviderConstants.Threads._ID,
                TelephonyProviderConstants.Threads.READ,
                TelephonyProviderConstants.Threads.RECIPIENT_IDS,
        };

        int _ID = 0;
        int READ = 1;
        int RECIPIENT_IDS = 2;
    }

    private interface CanonicalAddressQuery {
        String[] PROJECTION = {
                TelephonyProviderConstants.CanonicalAddresses._ID,
                TelephonyProviderConstants.CanonicalAddresses.ADDRESS,
        };

        int _ID = 0;
        int ADDRESS = 1;
    }

    private interface MmsSmsQuery {
        String[] PROJECTION = {
                TelephonyProviderConstants.Sms._ID,
                TelephonyProviderConstants.Sms.ADDRESS,
                TelephonyProviderConstants.Sms.PERSON,
                TelephonyProviderConstants.Sms.THREAD_ID,
        };

        int _ID = 0;
        int ADDRESS = 1;
        int PERSON = 2;
        int THREAD_ID = 3;
    }

    private interface MmsAddrQuery {
        String[] PROJECTION = {
                TelephonyProviderConstants.Mms.Addr.ADDRESS,
                TelephonyProviderConstants.Mms.Addr.CONTACT_ID,
        };

        int ADDRESS = 0;
        int CONTACT_ID = 1;
    }

    private interface RawContactsQuery {
        String[] PROJECTION = {
                ContactsContract.RawContacts.DISPLAY_NAME_PRIMARY,
        };

        int DISPLAY_NAME = 0;
    }

    private interface ContactsQuery {
        String[] PROJECTION = {
                ContactsContract.Contacts.DISPLAY_NAME,
        };

        int DISPLAY_NAME = 0;
    }
}




Java Source Code List

com.example.dashclock.exampleextension.ExampleExtension.java
com.example.dashclock.exampleextension.ExampleSettingsActivity.java
com.google.android.apps.dashclock.BackupAgent.java
com.google.android.apps.dashclock.DashClockService.java
com.google.android.apps.dashclock.DaydreamService.java
com.google.android.apps.dashclock.ExtensionHost.java
com.google.android.apps.dashclock.ExtensionManager.java
com.google.android.apps.dashclock.ExtensionPackageChangeReceiver.java
com.google.android.apps.dashclock.HelpUtils.java
com.google.android.apps.dashclock.LogAttachmentProvider.java
com.google.android.apps.dashclock.LogUtils.java
com.google.android.apps.dashclock.PeriodicExtensionRefreshReceiver.java
com.google.android.apps.dashclock.Utils.java
com.google.android.apps.dashclock.WidgetClickProxyActivity.java
com.google.android.apps.dashclock.WidgetProvider.java
com.google.android.apps.dashclock.api.DashClockExtension.java
com.google.android.apps.dashclock.api.ExtensionData.java
com.google.android.apps.dashclock.api.VisibleExtension.java
com.google.android.apps.dashclock.api.package-info.java
com.google.android.apps.dashclock.calendar.CalendarExtension.java
com.google.android.apps.dashclock.calendar.CalendarSelectionPreference.java
com.google.android.apps.dashclock.calendar.CalendarSettingsActivity.java
com.google.android.apps.dashclock.configuration.AppChooserPreference.java
com.google.android.apps.dashclock.configuration.AppearanceConfig.java
com.google.android.apps.dashclock.configuration.BaseSettingsActivity.java
com.google.android.apps.dashclock.configuration.ColorPreference.java
com.google.android.apps.dashclock.configuration.ConfigurationActivity.java
com.google.android.apps.dashclock.configuration.ConfigureAdvancedFragment.java
com.google.android.apps.dashclock.configuration.ConfigureAppearanceFragment.java
com.google.android.apps.dashclock.configuration.ConfigureDaydreamFragment.java
com.google.android.apps.dashclock.configuration.ConfigureExtensionsFragment.java
com.google.android.apps.dashclock.configuration.DaydreamProxyActivity.java
com.google.android.apps.dashclock.gmail.GmailContract.java
com.google.android.apps.dashclock.gmail.GmailExtension.java
com.google.android.apps.dashclock.gmail.GmailSettingsActivity.java
com.google.android.apps.dashclock.nextalarm.NextAlarmExtension.java
com.google.android.apps.dashclock.nextalarm.NextAlarmSettingsActivity.java
com.google.android.apps.dashclock.phone.MissedCallsExtension.java
com.google.android.apps.dashclock.phone.SmsExtension.java
com.google.android.apps.dashclock.phone.TelephonyProviderConstants.java
com.google.android.apps.dashclock.render.DashClockRenderer.java
com.google.android.apps.dashclock.render.SimpleRenderer.java
com.google.android.apps.dashclock.render.SimpleViewBuilder.java
com.google.android.apps.dashclock.render.ViewBuilder.java
com.google.android.apps.dashclock.render.WidgetRemoteViewsFactoryService.java
com.google.android.apps.dashclock.render.WidgetRenderer.java
com.google.android.apps.dashclock.render.WidgetViewBuilder.java
com.google.android.apps.dashclock.ui.DragGripView.java
com.google.android.apps.dashclock.ui.EdgeEffectUtil.java
com.google.android.apps.dashclock.ui.PagerPositionStrip.java
com.google.android.apps.dashclock.ui.SimplePagedTabsHelper.java
com.google.android.apps.dashclock.ui.SwipeDismissListViewTouchListener.java
com.google.android.apps.dashclock.ui.UndoBarController.java
com.google.android.apps.dashclock.weather.CantGetWeatherException.java
com.google.android.apps.dashclock.weather.WeatherData.java
com.google.android.apps.dashclock.weather.WeatherExtension.java
com.google.android.apps.dashclock.weather.WeatherLocationPreference.java
com.google.android.apps.dashclock.weather.WeatherRetryReceiver.java
com.google.android.apps.dashclock.weather.WeatherSettingsActivity.java
com.google.android.apps.dashclock.weather.YahooWeatherApiClient.java
com.google.android.apps.dashclock.weather.YahooWeatherApiConfig.java