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            try {
70                threadIdStrings.add(String.valueOf(
71                        Telephony.Threads.getOrCreateThreadId(getContext(), phone)));
72            } catch (Exception e) {
73                // Do nothing. Telephony.Threads.getOrCreateThreadId() throws exceptions when
74                // it can't find/create a threadId (b/17657656).
75            }
76        }
77
78        // Query the SMS database for the threads
79        Cursor cursor = getSmsCursorFromThreads(threadIdStrings);
80        if (cursor != null) {
81            try {
82                List<ContactInteraction> interactions = new ArrayList<>();
83                while (cursor.moveToNext()) {
84                    ContentValues values = new ContentValues();
85                    DatabaseUtils.cursorRowToContentValues(cursor, values);
86                    interactions.add(new SmsInteraction(values));
87                }
88
89                return interactions;
90            } finally {
91                cursor.close();
92            }
93        }
94
95        return Collections.emptyList();
96    }
97
98    /**
99     * Return the most recent messages between a list of threads
100     */
101    private Cursor getSmsCursorFromThreads(List<String> threadIds) {
102        if (threadIds.size() == 0) {
103            return null;
104        }
105        String selection = Telephony.Sms.THREAD_ID + " IN "
106                + ContactInteractionUtil.questionMarks(threadIds.size());
107
108        return getContext().getContentResolver().query(
109                Telephony.Sms.CONTENT_URI,
110                /* projection = */ null,
111                selection,
112                threadIds.toArray(new String[threadIds.size()]),
113                Telephony.Sms.DEFAULT_SORT_ORDER
114                        + " LIMIT " + mMaxToRetrieve);
115    }
116
117    @Override
118    protected void onStartLoading() {
119        super.onStartLoading();
120
121        if (mData != null) {
122            deliverResult(mData);
123        }
124
125        if (takeContentChanged() || mData == null) {
126            forceLoad();
127        }
128    }
129
130    @Override
131    protected void onStopLoading() {
132        // Attempt to cancel the current load task if possible.
133        cancelLoad();
134    }
135
136    @Override
137    public void deliverResult(List<ContactInteraction> data) {
138        mData = data;
139        if (isStarted()) {
140            super.deliverResult(data);
141        }
142    }
143
144    @Override
145    protected void onReset() {
146        super.onReset();
147
148        // Ensure the loader is stopped
149        onStopLoading();
150        if (mData != null) {
151            mData.clear();
152        }
153    }
154}
155