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