1 /*
2  * Copyright (C) 2016 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 package com.android.contacts.tests;
17 
18 import static org.hamcrest.Matchers.equalTo;
19 import static org.junit.Assume.assumeThat;
20 import static org.junit.Assume.assumeTrue;
21 
22 import android.content.ContentProviderOperation;
23 import android.content.ContentProviderResult;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.OperationApplicationException;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.RemoteException;
31 import android.telephony.TelephonyManager;
32 
33 import androidx.annotation.NonNull;
34 import androidx.test.InstrumentationRegistry;
35 
36 import com.android.contacts.database.SimContactDao;
37 import com.android.contacts.database.SimContactDaoImpl;
38 import com.android.contacts.model.SimCard;
39 import com.android.contacts.model.SimContact;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 public class SimContactsTestHelper {
45 
46     private final Context mContext;
47     private final TelephonyManager mTelephonyManager;
48     private final ContentResolver mResolver;
49     private final SimContactDao mSimDao;
50 
SimContactsTestHelper()51     public SimContactsTestHelper() {
52         this(InstrumentationRegistry.getTargetContext());
53     }
54 
SimContactsTestHelper(Context context)55     public SimContactsTestHelper(Context context) {
56         mContext = context;
57         mResolver = context.getContentResolver();
58         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
59         mSimDao = SimContactDao.create(context);
60     }
61 
getSimContactCount()62     public int getSimContactCount() {
63         Cursor cursor = mContext.getContentResolver().query(SimContactDaoImpl.ICC_CONTENT_URI,
64                 null, null, null, null);
65         try {
66             return cursor.getCount();
67         } finally {
68             cursor.close();
69         }
70     }
71 
addSimContact(String name, String number)72     public Uri addSimContact(String name, String number) {
73         ContentValues values = new ContentValues();
74         // Oddly even though it's called name when querying we have to use "tag" for it to work
75         // when inserting.
76         if (name != null) {
77             values.put("tag", name);
78         }
79         if (number != null) {
80             values.put(SimContactDaoImpl.NUMBER, number);
81         }
82         return mResolver.insert(SimContactDaoImpl.ICC_CONTENT_URI, values);
83     }
84 
deleteAllSimContacts()85     public ContentProviderResult[] deleteAllSimContacts()
86             throws RemoteException, OperationApplicationException {
87         final List<SimCard> sims = mSimDao.getSimCards();
88         if (sims.isEmpty()) {
89             throw new IllegalStateException("Expected SIM card");
90         }
91         final List<SimContact> contacts = mSimDao.loadContactsForSim(sims.get(0));
92         ArrayList<ContentProviderOperation> ops = new ArrayList<>();
93         for (SimContact contact : contacts) {
94             ops.add(ContentProviderOperation
95                     .newDelete(SimContactDaoImpl.ICC_CONTENT_URI)
96                     .withSelection(getWriteSelection(contact), null)
97                     .build());
98         }
99         return mResolver.applyBatch(SimContactDaoImpl.ICC_CONTENT_URI.getAuthority(), ops);
100     }
101 
restore(ArrayList<ContentProviderOperation> restoreOps)102     public ContentProviderResult[] restore(ArrayList<ContentProviderOperation> restoreOps)
103             throws RemoteException, OperationApplicationException {
104         if (restoreOps == null) return null;
105 
106         // Remove SIM contacts because we assume that caller wants the data to be in the exact
107         // state as when the restore ops were captured.
108         deleteAllSimContacts();
109         return mResolver.applyBatch(SimContactDaoImpl.ICC_CONTENT_URI.getAuthority(), restoreOps);
110     }
111 
captureRestoreSnapshot()112     public ArrayList<ContentProviderOperation> captureRestoreSnapshot() {
113         final List<SimCard> sims = mSimDao.getSimCards();
114         if (sims.isEmpty()) {
115             throw new IllegalStateException("Expected SIM card");
116         }
117         final ArrayList<SimContact> contacts = mSimDao.loadContactsForSim(sims.get(0));
118 
119         final ArrayList<ContentProviderOperation> ops = new ArrayList<>();
120         for (SimContact contact : contacts) {
121             final String[] emails = contact.getEmails();
122             if (emails != null && emails.length > 0) {
123                 throw new IllegalStateException("Cannot restore emails." +
124                         " Please manually remove SIM contacts with emails.");
125             }
126             ops.add(ContentProviderOperation
127                     .newInsert(SimContactDaoImpl.ICC_CONTENT_URI)
128                     .withValue("tag", contact.getName())
129                     .withValue("number", contact.getPhone())
130                     .build());
131         }
132         return ops;
133     }
134 
getWriteSelection(SimContact simContact)135     public String getWriteSelection(SimContact simContact) {
136         return "tag='" + simContact.getName() + "' AND " + SimContactDaoImpl.NUMBER + "='" +
137                 simContact.getPhone() + "'";
138     }
139 
deleteSimContact(@onNull String name, @NonNull String number)140     public int deleteSimContact(@NonNull  String name, @NonNull  String number) {
141         // IccProvider doesn't use the selection args.
142         final String selection = "tag='" + name + "' AND " +
143                 SimContactDaoImpl.NUMBER + "='" + number + "'";
144         return mResolver.delete(SimContactDaoImpl.ICC_CONTENT_URI, selection, null);
145     }
146 
isSimReady()147     public boolean isSimReady() {
148         return mTelephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY;
149     }
150 
doesSimHaveContacts()151     public boolean doesSimHaveContacts() {
152         return isSimReady() && getSimContactCount() > 0;
153     }
154 
isSimWritable()155     public boolean isSimWritable() {
156         if (!isSimReady()) return false;
157         final String name = "writabeProbe" + System.nanoTime();
158         final Uri uri = addSimContact(name, "15095550101");
159         return uri != null && deleteSimContact(name, "15095550101") == 1;
160     }
161 
assumeSimReady()162     public void assumeSimReady() {
163         assumeTrue(isSimReady());
164     }
165 
assumeHasSimContacts()166     public void assumeHasSimContacts() {
167         assumeTrue(doesSimHaveContacts());
168     }
169 
assumeSimCardAbsent()170     public void assumeSimCardAbsent() {
171         assumeThat(mTelephonyManager.getSimState(), equalTo(TelephonyManager.SIM_STATE_ABSENT));
172     }
173 
174     // The emulator reports SIM_STATE_READY but writes are ignored. This verifies that the
175     // device will actually persist writes to the SIM card.
assumeSimWritable()176     public void assumeSimWritable() {
177         assumeSimReady();
178         assumeTrue(isSimWritable());
179     }
180 }
181