Home | History | Annotate | Download | only in interactions
      1 /*
      2  * Copyright (C) 2014 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 package com.android.contacts.interactions;
     17 
     18 import android.content.AsyncTaskLoader;
     19 import android.content.ContentValues;
     20 import android.content.Context;
     21 import android.content.pm.PackageManager;
     22 import android.database.Cursor;
     23 import android.database.DatabaseUtils;
     24 import android.provider.Telephony;
     25 import android.util.Log;
     26 
     27 import com.android.contacts.compat.TelephonyThreadsCompat;
     28 
     29 import java.util.ArrayList;
     30 import java.util.Collections;
     31 import java.util.List;
     32 
     33 /**
     34  * Loads the most recent sms between the passed in phone numbers.
     35  *
     36  * This is a two part process. The first step is retrieving the threadIds for each of the phone
     37  * numbers using fuzzy matching. The next step is to run another query against these threadIds
     38  * to retrieve the actual sms.
     39  */
     40 public class SmsInteractionsLoader extends AsyncTaskLoader<List<ContactInteraction>> {
     41 
     42     private static final String TAG = SmsInteractionsLoader.class.getSimpleName();
     43 
     44     private String[] mPhoneNums;
     45     private int mMaxToRetrieve;
     46     private List<ContactInteraction> mData;
     47 
     48     /**
     49      * Loads a list of SmsInteraction from the supplied phone numbers.
     50      */
     51     public SmsInteractionsLoader(Context context, String[] phoneNums,
     52             int maxToRetrieve) {
     53         super(context);
     54         if (Log.isLoggable(TAG, Log.VERBOSE)) {
     55             Log.v(TAG, "SmsInteractionsLoader");
     56         }
     57         mPhoneNums = phoneNums;
     58         mMaxToRetrieve = maxToRetrieve;
     59     }
     60 
     61     @Override
     62     public List<ContactInteraction> loadInBackground() {
     63         if (Log.isLoggable(TAG, Log.VERBOSE)) {
     64             Log.v(TAG, "loadInBackground");
     65         }
     66         // Confirm the device has Telephony and numbers were provided before proceeding
     67         if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
     68                 || mPhoneNums == null || mPhoneNums.length == 0) {
     69             return Collections.emptyList();
     70         }
     71 
     72         // Retrieve the thread IDs
     73         List<String> threadIdStrings = new ArrayList<>();
     74         for (String phone : mPhoneNums) {
     75             // TODO: the phone numbers added to the ContactInteraction result should retain their
     76             // original formatting since TalkBack is not reading the normalized numbers correctly
     77             try {
     78                 threadIdStrings.add(String.valueOf(
     79                         TelephonyThreadsCompat.getOrCreateThreadId(getContext(), phone)));
     80             } catch (Exception e) {
     81                 // Do nothing. Telephony.Threads.getOrCreateThreadId() throws exceptions when
     82                 // it can't find/create a threadId (b/17657656).
     83             }
     84         }
     85 
     86         // Query the SMS database for the threads
     87         Cursor cursor = getSmsCursorFromThreads(threadIdStrings);
     88         if (cursor != null) {
     89             try {
     90                 List<ContactInteraction> interactions = new ArrayList<>();
     91                 while (cursor.moveToNext()) {
     92                     ContentValues values = new ContentValues();
     93                     DatabaseUtils.cursorRowToContentValues(cursor, values);
     94                     interactions.add(new SmsInteraction(values));
     95                 }
     96 
     97                 return interactions;
     98             } finally {
     99                 cursor.close();
    100             }
    101         }
    102 
    103         return Collections.emptyList();
    104     }
    105 
    106     /**
    107      * Return the most recent messages between a list of threads
    108      */
    109     private Cursor getSmsCursorFromThreads(List<String> threadIds) {
    110         if (threadIds.size() == 0) {
    111             return null;
    112         }
    113         String selection = Telephony.Sms.THREAD_ID + " IN "
    114                 + ContactInteractionUtil.questionMarks(threadIds.size());
    115 
    116         return getContext().getContentResolver().query(
    117                 Telephony.Sms.CONTENT_URI,
    118                 /* projection = */ null,
    119                 selection,
    120                 threadIds.toArray(new String[threadIds.size()]),
    121                 Telephony.Sms.DEFAULT_SORT_ORDER
    122                         + " LIMIT " + mMaxToRetrieve);
    123     }
    124 
    125     @Override
    126     protected void onStartLoading() {
    127         super.onStartLoading();
    128 
    129         if (mData != null) {
    130             deliverResult(mData);
    131         }
    132 
    133         if (takeContentChanged() || mData == null) {
    134             forceLoad();
    135         }
    136     }
    137 
    138     @Override
    139     protected void onStopLoading() {
    140         // Attempt to cancel the current load task if possible.
    141         cancelLoad();
    142     }
    143 
    144     @Override
    145     public void deliverResult(List<ContactInteraction> data) {
    146         mData = data;
    147         if (isStarted()) {
    148             super.deliverResult(data);
    149         }
    150     }
    151 
    152     @Override
    153     protected void onReset() {
    154         super.onReset();
    155 
    156         // Ensure the loader is stopped
    157         onStopLoading();
    158         if (mData != null) {
    159             mData.clear();
    160         }
    161     }
    162 }
    163