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