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