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