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 com.android.contacts.common.compat.TelephonyThreadsCompat; 28 29import java.util.ArrayList; 30import java.util.Collections; 31import 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 */ 40public 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 Log.v(TAG, "SmsInteractionsLoader"); 55 mPhoneNums = phoneNums; 56 mMaxToRetrieve = maxToRetrieve; 57 } 58 59 @Override 60 public List<ContactInteraction> loadInBackground() { 61 Log.v(TAG, "loadInBackground"); 62 // Confirm the device has Telephony and numbers were provided before proceeding 63 if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY) 64 || mPhoneNums == null || mPhoneNums.length == 0) { 65 return Collections.emptyList(); 66 } 67 68 // Retrieve the thread IDs 69 List<String> threadIdStrings = new ArrayList<>(); 70 for (String phone : mPhoneNums) { 71 // TODO: the phone numbers added to the ContactInteraction result should retain their 72 // original formatting since TalkBack is not reading the normalized numbers correctly 73 try { 74 threadIdStrings.add(String.valueOf( 75 TelephonyThreadsCompat.getOrCreateThreadId(getContext(), phone))); 76 } catch (Exception e) { 77 // Do nothing. Telephony.Threads.getOrCreateThreadId() throws exceptions when 78 // it can't find/create a threadId (b/17657656). 79 } 80 } 81 82 // Query the SMS database for the threads 83 Cursor cursor = getSmsCursorFromThreads(threadIdStrings); 84 if (cursor != null) { 85 try { 86 List<ContactInteraction> interactions = new ArrayList<>(); 87 while (cursor.moveToNext()) { 88 ContentValues values = new ContentValues(); 89 DatabaseUtils.cursorRowToContentValues(cursor, values); 90 interactions.add(new SmsInteraction(values)); 91 } 92 93 return interactions; 94 } finally { 95 cursor.close(); 96 } 97 } 98 99 return Collections.emptyList(); 100 } 101 102 /** 103 * Return the most recent messages between a list of threads 104 */ 105 private Cursor getSmsCursorFromThreads(List<String> threadIds) { 106 if (threadIds.size() == 0) { 107 return null; 108 } 109 String selection = Telephony.Sms.THREAD_ID + " IN " 110 + ContactInteractionUtil.questionMarks(threadIds.size()); 111 112 return getContext().getContentResolver().query( 113 Telephony.Sms.CONTENT_URI, 114 /* projection = */ null, 115 selection, 116 threadIds.toArray(new String[threadIds.size()]), 117 Telephony.Sms.DEFAULT_SORT_ORDER 118 + " LIMIT " + mMaxToRetrieve); 119 } 120 121 @Override 122 protected void onStartLoading() { 123 super.onStartLoading(); 124 125 if (mData != null) { 126 deliverResult(mData); 127 } 128 129 if (takeContentChanged() || mData == null) { 130 forceLoad(); 131 } 132 } 133 134 @Override 135 protected void onStopLoading() { 136 // Attempt to cancel the current load task if possible. 137 cancelLoad(); 138 } 139 140 @Override 141 public void deliverResult(List<ContactInteraction> data) { 142 mData = data; 143 if (isStarted()) { 144 super.deliverResult(data); 145 } 146 } 147 148 @Override 149 protected void onReset() { 150 super.onReset(); 151 152 // Ensure the loader is stopped 153 onStopLoading(); 154 if (mData != null) { 155 mData.clear(); 156 } 157 } 158} 159