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.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        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