1b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos/*
2b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * Copyright (C) 2014 The Android Open Source Project
3b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos *
4b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * Licensed under the Apache License, Version 2.0 (the "License");
5b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * you may not use this file except in compliance with the License.
6b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * You may obtain a copy of the License at
7b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos *
8b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos *      http://www.apache.org/licenses/LICENSE-2.0
9b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos *
10b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * Unless required by applicable law or agreed to in writing, software
11b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * distributed under the License is distributed on an "AS IS" BASIS,
12b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * See the License for the specific language governing permissions and
14b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * limitations under the License.
15b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos */
16b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulospackage com.android.contacts.interactions;
17b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
18b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.content.AsyncTaskLoader;
19b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.content.ContentValues;
20b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.content.Context;
21b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.content.pm.PackageManager;
22b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.database.Cursor;
23b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.database.DatabaseUtils;
24b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.provider.Telephony;
25b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport android.util.Log;
26b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
27047197228d7e1112efdd5524f3e3b58926b848f6Wenyi Wangimport com.android.contacts.common.compat.TelephonyThreadsCompat;
28047197228d7e1112efdd5524f3e3b58926b848f6Wenyi Wang
29b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport java.util.ArrayList;
30b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport java.util.Collections;
31b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulosimport java.util.List;
32b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
33b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos/**
34b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * Loads the most recent sms between the passed in phone numbers.
35b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos *
36b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * This is a two part process. The first step is retrieving the threadIds for each of the phone
37b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * numbers using fuzzy matching. The next step is to run another query against these threadIds
38b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos * to retrieve the actual sms.
39b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos */
40b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulospublic class SmsInteractionsLoader extends AsyncTaskLoader<List<ContactInteraction>> {
41b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
42b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    private static final String TAG = SmsInteractionsLoader.class.getSimpleName();
43b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
44b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    private String[] mPhoneNums;
45b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    private int mMaxToRetrieve;
46b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    private List<ContactInteraction> mData;
47b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
48b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    /**
49b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos     * Loads a list of SmsInteraction from the supplied phone numbers.
50b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos     */
51b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    public SmsInteractionsLoader(Context context, String[] phoneNums,
52b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            int maxToRetrieve) {
53b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        super(context);
54b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        Log.v(TAG, "SmsInteractionsLoader");
55b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        mPhoneNums = phoneNums;
56b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        mMaxToRetrieve = maxToRetrieve;
57b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
58b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
59b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    @Override
60b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    public List<ContactInteraction> loadInBackground() {
61b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        Log.v(TAG, "loadInBackground");
62b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        // Confirm the device has Telephony and numbers were provided before proceeding
63b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        if (!getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
64b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                || mPhoneNums == null || mPhoneNums.length == 0) {
65b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            return Collections.emptyList();
66b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
67b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
68b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        // Retrieve the thread IDs
69b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        List<String> threadIdStrings = new ArrayList<>();
70b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        for (String phone : mPhoneNums) {
718a3fe52802135be2402a5b216325615fb796a509Walter Jang            // TODO: the phone numbers added to the ContactInteraction result should retain their
728a3fe52802135be2402a5b216325615fb796a509Walter Jang            // original formatting since TalkBack is not reading the normalized numbers correctly
73ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell            try {
74ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell                threadIdStrings.add(String.valueOf(
75047197228d7e1112efdd5524f3e3b58926b848f6Wenyi Wang                        TelephonyThreadsCompat.getOrCreateThreadId(getContext(), phone)));
76ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell            } catch (Exception e) {
77ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell                // Do nothing. Telephony.Threads.getOrCreateThreadId() throws exceptions when
78ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell                // it can't find/create a threadId (b/17657656).
79ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell            }
80b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
81b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
82b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        // Query the SMS database for the threads
83b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        Cursor cursor = getSmsCursorFromThreads(threadIdStrings);
84899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos        if (cursor != null) {
85899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos            try {
86899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                List<ContactInteraction> interactions = new ArrayList<>();
87899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                while (cursor.moveToNext()) {
88899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                    ContentValues values = new ContentValues();
89899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                    DatabaseUtils.cursorRowToContentValues(cursor, values);
90899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                    interactions.add(new SmsInteraction(values));
91899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                }
92b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
93899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                return interactions;
94899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos            } finally {
95899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos                cursor.close();
96899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos            }
97b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
98b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
99899aa21e911ee7170beab228d44d7fed68c414e4Paul Soulos        return Collections.emptyList();
100b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
101b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
102b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    /**
103b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos     * Return the most recent messages between a list of threads
104b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos     */
105b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    private Cursor getSmsCursorFromThreads(List<String> threadIds) {
106ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell        if (threadIds.size() == 0) {
107ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell            return null;
108ae6c7170daf92a91b9a189a985cd6d06817d92bdBrian Attwell        }
109b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        String selection = Telephony.Sms.THREAD_ID + " IN "
110b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                + ContactInteractionUtil.questionMarks(threadIds.size());
111b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
112b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        return getContext().getContentResolver().query(
113b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                Telephony.Sms.CONTENT_URI,
114b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                /* projection = */ null,
115b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                selection,
116b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                threadIds.toArray(new String[threadIds.size()]),
117b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                Telephony.Sms.DEFAULT_SORT_ORDER
118b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos                        + " LIMIT " + mMaxToRetrieve);
119b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
120b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
121b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    @Override
122b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    protected void onStartLoading() {
123b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        super.onStartLoading();
124b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
125b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        if (mData != null) {
126b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            deliverResult(mData);
127b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
128b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
129b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        if (takeContentChanged() || mData == null) {
130b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            forceLoad();
131b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
132b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
133b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
134b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    @Override
135b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    protected void onStopLoading() {
136b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        // Attempt to cancel the current load task if possible.
137b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        cancelLoad();
138b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
139b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
140b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    @Override
141b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    public void deliverResult(List<ContactInteraction> data) {
142b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        mData = data;
143b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        if (isStarted()) {
144b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            super.deliverResult(data);
145b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
146b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
147b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
148b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    @Override
149b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    protected void onReset() {
150b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        super.onReset();
151b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos
152b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        // Ensure the loader is stopped
153b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        onStopLoading();
154b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        if (mData != null) {
155b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos            mData.clear();
156b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos        }
157b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos    }
158b3054e551173887029c55cb10b83f1afb7f8a6fePaul Soulos}
159