Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.common.contacts;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.database.Cursor;
     23 import android.net.Uri;
     24 import android.os.Build;
     25 import android.provider.ContactsContract;
     26 import android.provider.ContactsContract.CommonDataKinds.Email;
     27 import android.provider.ContactsContract.CommonDataKinds.Phone;
     28 import android.provider.ContactsContract.Data;
     29 import android.text.TextUtils;
     30 import android.text.util.Rfc822Token;
     31 import android.text.util.Rfc822Tokenizer;
     32 import android.util.Log;
     33 import android.widget.TextView;
     34 
     35 import java.util.ArrayList;
     36 import java.util.Arrays;
     37 import java.util.Collection;
     38 import java.util.HashSet;
     39 import java.util.Set;
     40 
     41 /**
     42  * Convenient class for updating usage statistics in ContactsProvider.
     43  *
     44  * Applications like Email, Sms, etc. can promote recipients for better sorting with this class
     45  *
     46  * @see ContactsContract.Contacts
     47  */
     48 public class DataUsageStatUpdater {
     49     private static final String TAG = DataUsageStatUpdater.class.getSimpleName();
     50 
     51     /**
     52      * Copied from API in ICS (not available before it). You can use values here if you are sure
     53      * it is supported by the device.
     54      */
     55     public static final class DataUsageFeedback {
     56         static final Uri FEEDBACK_URI =
     57             Uri.withAppendedPath(Data.CONTENT_URI, "usagefeedback");
     58 
     59         static final String USAGE_TYPE = "type";
     60         public static final String USAGE_TYPE_CALL = "call";
     61         public static final String USAGE_TYPE_LONG_TEXT = "long_text";
     62         public static final String USAGE_TYPE_SHORT_TEXT = "short_text";
     63     }
     64 
     65     private final ContentResolver mResolver;
     66 
     67     public DataUsageStatUpdater(Context context) {
     68         mResolver = context.getContentResolver();
     69     }
     70 
     71     /**
     72      * Updates usage statistics using comma-separated RFC822 address like
     73      * "Joe <joe (at) example.com>, Due <due (at) example.com>".
     74      *
     75      * This will cause Disk access so should be called in a background thread.
     76      *
     77      * @return true when update request is correctly sent. False when the request fails,
     78      * input has no valid entities.
     79      */
     80     public boolean updateWithRfc822Address(Collection<CharSequence> texts){
     81         if (texts == null) {
     82             return false;
     83         } else {
     84             final Set<String> addresses = new HashSet<String>();
     85             for (CharSequence text : texts) {
     86                 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text.toString().trim());
     87                 for (Rfc822Token token : tokens) {
     88                     addresses.add(token.getAddress());
     89                 }
     90             }
     91             return updateWithAddress(addresses);
     92         }
     93     }
     94 
     95     /**
     96      * Update usage statistics information using a list of email addresses.
     97      *
     98      * This will cause Disk access so should be called in a background thread.
     99      *
    100      * @see #update(Collection, Collection, String)
    101      *
    102      * @return true when update request is correctly sent. False when the request fails,
    103      * input has no valid entities.
    104      */
    105     public boolean updateWithAddress(Collection<String> addresses) {
    106         if (Log.isLoggable(TAG, Log.DEBUG)) {
    107             Log.d(TAG, "updateWithAddress: " + Arrays.toString(addresses.toArray()));
    108         }
    109         if (addresses != null && !addresses.isEmpty()) {
    110             final ArrayList<String> whereArgs = new ArrayList<String>();
    111             final StringBuilder whereBuilder = new StringBuilder();
    112             final String[] questionMarks = new String[addresses.size()];
    113 
    114             whereArgs.addAll(addresses);
    115             Arrays.fill(questionMarks, "?");
    116             // Email.ADDRESS == Email.DATA1. Email.ADDRESS can be available from API Level 11.
    117             whereBuilder.append(Email.DATA1 + " IN (")
    118                     .append(TextUtils.join(",", questionMarks))
    119                     .append(")");
    120             final Cursor cursor = mResolver.query(Email.CONTENT_URI,
    121                     new String[] {Email.CONTACT_ID, Email._ID}, whereBuilder.toString(),
    122                     whereArgs.toArray(new String[0]), null);
    123 
    124             if (cursor == null) {
    125                 Log.w(TAG, "Cursor for Email.CONTENT_URI became null.");
    126             } else {
    127                 final Set<Long> contactIds = new HashSet<Long>(cursor.getCount());
    128                 final Set<Long> dataIds = new HashSet<Long>(cursor.getCount());
    129                 try {
    130                     cursor.move(-1);
    131                     while(cursor.moveToNext()) {
    132                         contactIds.add(cursor.getLong(0));
    133                         dataIds.add(cursor.getLong(1));
    134                     }
    135                 } finally {
    136                     cursor.close();
    137                 }
    138                 return update(contactIds, dataIds, DataUsageFeedback.USAGE_TYPE_LONG_TEXT);
    139             }
    140         }
    141 
    142         return false;
    143     }
    144 
    145     /**
    146      * Update usage statistics information using a list of phone numbers.
    147      *
    148      * This will cause Disk access so should be called in a background thread.
    149      *
    150      * @see #update(Collection, Collection, String)
    151      *
    152      * @return true when update request is correctly sent. False when the request fails,
    153      * input has no valid entities.
    154      */
    155     public boolean updateWithPhoneNumber(Collection<String> numbers) {
    156         if (Log.isLoggable(TAG, Log.DEBUG)) {
    157             Log.d(TAG, "updateWithPhoneNumber: " + Arrays.toString(numbers.toArray()));
    158         }
    159         if (numbers != null && !numbers.isEmpty()) {
    160             final ArrayList<String> whereArgs = new ArrayList<String>();
    161             final StringBuilder whereBuilder = new StringBuilder();
    162             final String[] questionMarks = new String[numbers.size()];
    163 
    164             whereArgs.addAll(numbers);
    165             Arrays.fill(questionMarks, "?");
    166             // Phone.NUMBER == Phone.DATA1. NUMBER can be available from API Level 11.
    167             whereBuilder.append(Phone.DATA1 + " IN (")
    168                     .append(TextUtils.join(",", questionMarks))
    169                     .append(")");
    170             final Cursor cursor = mResolver.query(Phone.CONTENT_URI,
    171                     new String[] {Phone.CONTACT_ID, Phone._ID}, whereBuilder.toString(),
    172                     whereArgs.toArray(new String[0]), null);
    173 
    174             if (cursor == null) {
    175                 Log.w(TAG, "Cursor for Phone.CONTENT_URI became null.");
    176             } else {
    177                 final Set<Long> contactIds = new HashSet<Long>(cursor.getCount());
    178                 final Set<Long> dataIds = new HashSet<Long>(cursor.getCount());
    179                 try {
    180                     cursor.move(-1);
    181                     while(cursor.moveToNext()) {
    182                         contactIds.add(cursor.getLong(0));
    183                         dataIds.add(cursor.getLong(1));
    184                     }
    185                 } finally {
    186                     cursor.close();
    187                 }
    188                 return update(contactIds, dataIds, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT);
    189             }
    190         }
    191         return false;
    192     }
    193 
    194     /**
    195      * @return true when one or more of update requests are correctly sent.
    196      * False when all the requests fail.
    197      */
    198     private boolean update(Collection<Long> contactIds, Collection<Long> dataIds, String type) {
    199         final long currentTimeMillis = System.currentTimeMillis();
    200 
    201         boolean successful = false;
    202 
    203         // From ICS (SDK_INT 14) we can use per-contact-method structure. We'll check if the device
    204         // supports it and call the API.
    205         if (Build.VERSION.SDK_INT >= 14) {
    206             if (dataIds.isEmpty()) {
    207                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    208                     Log.d(TAG, "Given list for data IDs is null. Ignoring.");
    209                 }
    210             } else {
    211                 final Uri uri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
    212                         .appendPath(TextUtils.join(",", dataIds))
    213                         .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, type)
    214                         .build();
    215                 if (mResolver.update(uri, new ContentValues(), null, null) > 0) {
    216                     successful = true;
    217                 } else {
    218                     if (Log.isLoggable(TAG, Log.DEBUG)) {
    219                         Log.d(TAG, "update toward data rows " + dataIds + " failed");
    220                     }
    221                 }
    222             }
    223         } else {
    224             // Use older API.
    225             if (contactIds.isEmpty()) {
    226                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    227                     Log.d(TAG, "Given list for contact IDs is null. Ignoring.");
    228                 }
    229             } else {
    230                 final StringBuilder whereBuilder = new StringBuilder();
    231                 final ArrayList<String> whereArgs = new ArrayList<String>();
    232                 final String[] questionMarks = new String[contactIds.size()];
    233                 for (long contactId : contactIds) {
    234                     whereArgs.add(String.valueOf(contactId));
    235                 }
    236                 Arrays.fill(questionMarks, "?");
    237                 whereBuilder.append(ContactsContract.Contacts._ID + " IN (").
    238                         append(TextUtils.join(",", questionMarks)).
    239                         append(")");
    240 
    241                 if (Log.isLoggable(TAG, Log.DEBUG)) {
    242                     Log.d(TAG, "contactId where: " + whereBuilder.toString());
    243                     Log.d(TAG, "contactId selection: " + whereArgs);
    244                 }
    245 
    246                 final ContentValues values = new ContentValues();
    247                 values.put(ContactsContract.Contacts.LAST_TIME_CONTACTED, currentTimeMillis);
    248                 if (mResolver.update(ContactsContract.Contacts.CONTENT_URI, values,
    249                         whereBuilder.toString(), whereArgs.toArray(new String[0])) > 0) {
    250                     successful = true;
    251                 } else {
    252                     if (Log.isLoggable(TAG, Log.DEBUG)) {
    253                         Log.d(TAG, "update toward raw contacts " + contactIds + " failed");
    254                     }
    255                 }
    256             }
    257         }
    258 
    259         return successful;
    260     }
    261 }
    262