1 /*
2  * Copyright (C) 2015 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  */
16 
17 package com.android.messaging.datamodel;
18 
19 import android.content.ContentProvider;
20 import android.content.ContentValues;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.text.TextUtils;
24 
25 import androidx.test.filters.SmallTest;
26 
27 import com.android.messaging.BugleTestCase;
28 import com.android.messaging.FakeContentProvider;
29 import com.android.messaging.FakeContext;
30 import com.android.messaging.FakeFactory;
31 import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
32 import com.android.messaging.datamodel.data.ParticipantData;
33 import com.android.messaging.datamodel.data.ParticipantData.ParticipantsQuery;
34 import com.android.messaging.ui.UIIntents;
35 import com.android.messaging.util.ContactUtil;
36 
37 import org.junit.Assert;
38 import org.mockito.Mock;
39 
40 /**
41  * Utility class for testing ParticipantRefresh class for different scenarios.
42  */
43 @SmallTest
44 public class ParticipantRefreshTest extends BugleTestCase {
45     private FakeContext mContext;
46     FakeFactory mFakeFactory;
47     @Mock protected UIIntents mMockUIIntents;
48     protected FakeDataModel mFakeDataModel;
49 
50     @Override
setUp()51     public void setUp() throws Exception {
52         super.setUp();
53 
54         mContext = new FakeContext(getTestContext());
55 
56         final ContentProvider provider = new MessagingContentProvider();
57         provider.attachInfo(mContext, null);
58         mContext.addContentProvider(MessagingContentProvider.AUTHORITY, provider);
59 
60         mFakeDataModel = new FakeDataModel(mContext);
61         mFakeFactory = FakeFactory.registerWithFakeContext(getTestContext(), mContext)
62                 .withDataModel(mFakeDataModel)
63                 .withUIIntents(mMockUIIntents);
64     }
65 
66     /**
67      * Add some phonelookup result into take PhoneLookup content provider. This will be
68      * used for doing phone lookup during participant refresh.
69      */
addPhoneLookup(final String phone, final Object[][] lookupResult)70     private void addPhoneLookup(final String phone, final Object[][] lookupResult) {
71         final Uri uri = ContactUtil.lookupPhone(mContext, phone).getUri();
72         final FakeContentProvider phoneLookup = new FakeContentProvider(mContext,
73                 uri, false);
74         phoneLookup.addOverrideData(uri, null, null, ContactUtil.PhoneLookupQuery.PROJECTION,
75                 lookupResult);
76         mFakeFactory.withProvider(uri, phoneLookup);
77     }
78 
79     /**
80      * Add some participant to test database.
81      */
addParticipant(final String normalizedDestination, final long contactId, final String name, final String photoUrl)82     private void addParticipant(final String normalizedDestination, final long contactId,
83             final String name, final String photoUrl) {
84         final DatabaseWrapper db = DataModel.get().getDatabase();
85         final ContentValues values = new ContentValues();
86 
87         values.put(ParticipantColumns.NORMALIZED_DESTINATION, normalizedDestination);
88         values.put(ParticipantColumns.CONTACT_ID, contactId);
89         values.put(ParticipantColumns.FULL_NAME, name);
90         values.put(ParticipantColumns.PROFILE_PHOTO_URI, photoUrl);
91 
92         db.beginTransaction();
93         try {
94             db.insert(DatabaseHelper.PARTICIPANTS_TABLE, null, values);
95             db.setTransactionSuccessful();
96         } finally {
97             db.endTransaction();
98         }
99     }
100 
101     /**
102      * Verify that participant in the database has expected contacdtId, name and photoUrl fields.
103      */
verifyParticipant(final String normalizedDestination, final long contactId, final String name, final String photoUrl)104     private void verifyParticipant(final String normalizedDestination, final long contactId,
105             final String name, final String photoUrl) {
106         final DatabaseWrapper db = DataModel.get().getDatabase();
107         db.beginTransaction();
108         try {
109             final String selection = ParticipantColumns.NORMALIZED_DESTINATION + "=?";
110             final String[] selectionArgs = new String[] { normalizedDestination };
111 
112             final Cursor cursor = db.query(DatabaseHelper.PARTICIPANTS_TABLE,
113                     ParticipantsQuery.PROJECTION, selection, selectionArgs, null, null, null);
114 
115             if (cursor == null || cursor.getCount() != 1) {
116                 Assert.fail("Should have participants for:" + normalizedDestination);
117                 return;
118             }
119 
120             cursor.moveToFirst();
121             final int currentContactId = cursor.getInt(ParticipantsQuery.INDEX_CONTACT_ID);
122             final String currentName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
123             final String currentPhotoUrl =
124                     cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
125             if (currentContactId != contactId) {
126                 Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
127                         " expected=" + contactId + " actual=" + currentContactId);
128                 return;
129             }
130 
131             if (!TextUtils.equals(currentName, name)) {
132                 Assert.fail("Name doesn't match. normalizedNumber=" + normalizedDestination +
133                         " expected=" + name + " actual=" + currentName);
134                 return;
135             }
136 
137             if (!TextUtils.equals(currentPhotoUrl, photoUrl)) {
138                 Assert.fail("Contact Id doesn't match. normalizedNumber=" + normalizedDestination +
139                         " expected=" + photoUrl + " actual=" + currentPhotoUrl);
140                 return;
141             }
142 
143             db.setTransactionSuccessful();
144         } finally {
145             db.endTransaction();
146         }
147     }
148 
149     /**
150      * Verify that incremental refresh will resolve previously not resolved participants.
151      */
testIncrementalRefreshNotResolvedSingleMatch()152     public void testIncrementalRefreshNotResolvedSingleMatch() {
153         addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
154                 null, null);
155         addPhoneLookup("650-123-1233", new Object[][] {
156                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
157         });
158 
159         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
160         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
161     }
162 
163     /**
164      * Verify that incremental refresh will resolve previously not resolved participants.
165      */
testIncrementalRefreshNotResolvedMultiMatch()166     public void testIncrementalRefreshNotResolvedMultiMatch() {
167         addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED,
168                 null, null);
169         addPhoneLookup("650-123-1233", new Object[][] {
170                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
171                 { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
172         });
173 
174         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
175         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
176     }
177 
178     /**
179      * Verify that incremental refresh will not touch already-resolved participants.
180      */
testIncrementalRefreshResolvedSingleMatch()181     public void testIncrementalRefreshResolvedSingleMatch() {
182         addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
183         addPhoneLookup("650-123-1233", new Object[][] {
184                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
185         });
186 
187         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
188         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
189     }
190 
191     /**
192      * Verify that full refresh will correct already-resolved participants if needed
193      */
testFullRefreshResolvedSingleMatch()194     public void testFullRefreshResolvedSingleMatch() {
195         addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
196         addPhoneLookup("650-123-1233", new Object[][] {
197                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
198         });
199 
200         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
201         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
202     }
203 
204     /**
205      * Verify that incremental refresh will not touch participant that is marked as not found.
206      */
testIncrementalRefreshNotFound()207     public void testIncrementalRefreshNotFound() {
208         addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
209                 null, null);
210         addPhoneLookup("650-123-1233", new Object[][] {
211                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
212         });
213 
214         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_INCREMENTAL);
215         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
216     }
217 
218     /**
219      * Verify that full refresh will resolve participant that is marked as not found.
220      */
testFullRefreshNotFound()221     public void testFullRefreshNotFound() {
222         addParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
223                 null, null);
224         addPhoneLookup("650-123-1233", new Object[][] {
225                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null }
226         });
227 
228         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
229         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
230     }
231 
232     /**
233      * Verify that refresh take consideration of current contact_id when having multiple matches.
234      */
testFullRefreshResolvedMultiMatch1()235     public void testFullRefreshResolvedMultiMatch1() {
236         addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
237         addPhoneLookup("650-123-1233", new Object[][] {
238                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
239                 { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
240         });
241 
242         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
243         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
244     }
245 
246     /**
247      * Verify that refresh take consideration of current contact_id when having multiple matches.
248      */
testFullRefreshResolvedMultiMatch2()249     public void testFullRefreshResolvedMultiMatch2() {
250         addParticipant("650-123-1233", 2, "Joh", "content://photo/joh");
251         addPhoneLookup("650-123-1233", new Object[][] {
252                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
253                 { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
254         });
255 
256         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
257         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
258     }
259 
260     /**
261      * Verify that refresh take first contact in case current contact_id no longer matches.
262      */
testFullRefreshResolvedMultiMatch3()263     public void testFullRefreshResolvedMultiMatch3() {
264         addParticipant("650-123-1233", 3, "Joh", "content://photo/joh");
265         addPhoneLookup("650-123-1233", new Object[][] {
266                 { 1L, "John", "content://photo/john", "650-123-1233", null, null, null },
267                 { 2L, "Joe", "content://photo/joe", "650-123-1233", null, null, null }
268         });
269 
270         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
271         verifyParticipant("650-123-1233", 1, "John", "content://photo/john");
272     }
273 
274     /**
275      * Verify that refresh take first contact in case current contact_id no longer matches.
276      */
testFullRefreshResolvedBeforeButNotFoundNow()277     public void testFullRefreshResolvedBeforeButNotFoundNow() {
278         addParticipant("650-123-1233", 1, "Joh", "content://photo/joh");
279         addPhoneLookup("650-123-1233", new Object[][] {});
280 
281         ParticipantRefresh.refreshParticipants(ParticipantRefresh.REFRESH_MODE_FULL);
282         verifyParticipant("650-123-1233", ParticipantData.PARTICIPANT_CONTACT_ID_NOT_FOUND,
283                 null, null);
284     }
285 }
286