• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.providers.contacts;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteDatabase;
24 import android.net.Uri;
25 import android.os.FileUtils;
26 import android.provider.ContactsContract.Contacts;
27 import android.provider.ContactsContract.Profile;
28 import android.provider.ContactsContract.RawContacts;
29 import android.util.Log;
30 
31 import androidx.annotation.Nullable;
32 
33 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
34 
35 import junit.framework.Assert;
36 
37 import java.io.BufferedReader;
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 
44 public class TestUtils {
45     private static final String TAG = "ContactsTestUtils";
46 
TestUtils()47     private TestUtils() {
48     }
49 
50     /**
51      * We normally use in-memory DBs in unit tests, because that's faster, but it's impossible to
52      * look at intermediate databases when something is failing.  When this flag is set to true,
53      * we'll switch to file-based DBs, so we can call {@link #createDatabaseSnapshot}
54      * , pull the snapshot DBs and take a look at them.
55      */
56     public static final boolean ENABLE_DATABASE_SNAPSHOT = false; // DO NOT SUBMIT WITH TRUE.
57 
58     private static final Object sDatabasePathLock = new Object();
59     private static File sDatabasePath = null;
60 
getDatabaseFile(Context context, @Nullable String name)61     private static String getDatabaseFile(Context context, @Nullable String name) {
62         if (!ENABLE_DATABASE_SNAPSHOT) {
63             return null; // Use the in-memory DB.
64         }
65         synchronized (sDatabasePathLock) {
66             if (sDatabasePath == null) {
67                 final File path = new File(context.getCacheDir(), "test-db");
68                 if (path.exists()) {
69                     Assert.assertTrue("Unable to delete directory: " + path,
70                             FileUtils.deleteContents(path));
71                 } else {
72                     Assert.assertTrue("Unable to create directory: " + path, path.mkdirs());
73                 }
74                 Log.i(TAG, "Test DB directory: " + path);
75 
76                 sDatabasePath = path;
77             }
78             final File ret;
79             if (name == null) {
80                 ret = sDatabasePath;
81             } else {
82                 ret = new File(sDatabasePath, name);
83                 Log.i(TAG, "Test DB file: " + ret);
84             }
85             return ret.getAbsolutePath();
86         }
87     }
88 
getContactsDatabaseFilename(Context context)89     public static String getContactsDatabaseFilename(Context context) {
90         return getContactsDatabaseFilename(context, "");
91     }
92 
getContactsDatabaseFilename(Context context, String suffix)93     public static String getContactsDatabaseFilename(Context context, String suffix) {
94         return getDatabaseFile(context, "contacts2" + suffix + ".db");
95     }
96 
getProfileDatabaseFilename(Context context)97     public static String getProfileDatabaseFilename(Context context) {
98         return getProfileDatabaseFilename(context, "");
99     }
100 
getProfileDatabaseFilename(Context context, String suffix)101     public static String getProfileDatabaseFilename(Context context, String suffix) {
102         return getDatabaseFile(context, "profile.db" + suffix + ".db");
103     }
104 
createDatabaseSnapshot(Context context, String name)105     public static void createDatabaseSnapshot(Context context, String name) {
106         Assert.assertTrue(
107                 "ENABLE_DATABASE_SNAPSHOT must be set to true to create database snapshot",
108                 ENABLE_DATABASE_SNAPSHOT);
109 
110         final File fromDir = new File(getDatabaseFile(context, null));
111         final File toDir = new File(context.getCacheDir(), "snapshot-" + name);
112         if (toDir.exists()) {
113             Assert.assertTrue("Unable to delete directory: " + toDir,
114                     FileUtils.deleteContents(toDir));
115         } else {
116             Assert.assertTrue("Unable to create directory: " + toDir, toDir.mkdirs());
117         }
118 
119         Log.w(TAG, "Copying database files from '" + fromDir + "' into '" + toDir + "'...");
120 
121         for (File file : fromDir.listFiles()) {
122             try {
123                 final File to = new File(toDir, file.getName());
124                 FileUtils.copyFileOrThrow(file, to);
125                 Log.i(TAG, "Created: " + to);
126             } catch (IOException e) {
127                 Assert.fail("Failed to copy file: " + e.toString());
128             }
129         }
130     }
131 
132     /** Convenient method to create a ContentValues */
cv(Object... namesAndValues)133     public static ContentValues cv(Object... namesAndValues) {
134         Assert.assertTrue((namesAndValues.length % 2) == 0);
135 
136         final ContentValues ret = new ContentValues();
137         for (int i = 1; i < namesAndValues.length; i += 2) {
138             final String name = namesAndValues[i - 1].toString();
139             final Object value =  namesAndValues[i];
140             if (value == null) {
141                 ret.putNull(name);
142             } else if (value instanceof String) {
143                 ret.put(name, (String) value);
144             } else if (value instanceof Integer) {
145                 ret.put(name, (Integer) value);
146             } else if (value instanceof Long) {
147                 ret.put(name, (Long) value);
148             } else {
149                 Assert.fail("Unsupported type: " + value.getClass().getSimpleName());
150             }
151         }
152         return ret;
153     }
154 
155     /**
156      * Writes the content of a cursor to the log.
157      */
dumpCursor(Cursor c)158     public static final void dumpCursor(Cursor c) {
159         final StringBuilder sb = new StringBuilder();
160         for (int i = 0; i < c.getColumnCount(); i++) {
161             if (i > 0) sb.append("|");
162             sb.append(c.getColumnName(i));
163         }
164         Log.i(TAG, sb.toString());
165 
166         final int pos = c.getPosition();
167 
168         c.moveToPosition(-1);
169         while (c.moveToNext()) {
170             sb.setLength(0);
171             for (int i = 0; i < c.getColumnCount(); i++) {
172                 if (i > 0) sb.append("|");
173 
174                 if (c.getType(i) == Cursor.FIELD_TYPE_BLOB) {
175                     byte[] blob = c.getBlob(i);
176                     sb.append("([blob] ");
177                     sb.append(blob == null ? "null" : blob.length + "b");
178                     sb.append(")");
179                 } else {
180                     sb.append(c.getString(i));
181                 }
182             }
183             Log.i(TAG, sb.toString());
184         }
185 
186         c.moveToPosition(pos);
187     }
188 
dumpTable(SQLiteDatabase db, String name)189     public static void dumpTable(SQLiteDatabase db, String name) {
190         Log.i(TAG, "Dumping table: " + name);
191         try (Cursor c = db.rawQuery(String.format("SELECT * FROM %s", name), null)) {
192             dumpCursor(c);
193         }
194     }
195 
dumpUri(Context context, Uri uri)196     public static void dumpUri(Context context, Uri uri) {
197         Log.i(TAG, "Dumping URI: " + uri);
198         try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
199             dumpCursor(c);
200         }
201     }
202 
dumpUri(ContentResolver resolver, Uri uri)203     public static void dumpUri(ContentResolver resolver, Uri uri) {
204         Log.i(TAG, "Dumping URI: " + uri);
205         try (Cursor c = resolver.query(uri, null, null, null, null)) {
206             dumpCursor(c);
207         }
208     }
209 
210     /**
211      * Writes an arbitrary byte array to the test apk's cache directory.
212      */
dumpToCacheDir(Context context, String prefix, String suffix, byte[] data)213     public static final String dumpToCacheDir(Context context, String prefix, String suffix,
214             byte[] data) {
215         try {
216             File file = File.createTempFile(prefix, suffix, context.getCacheDir());
217             FileOutputStream fos = new FileOutputStream(file);
218             fos.write(data);
219             fos.close();
220             return file.getAbsolutePath();
221         } catch (IOException e) {
222             return "[Failed to write to file: " + e.getMessage() + "]";
223         }
224     }
225 
insertRawContact( ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values)226     public static Uri insertRawContact(
227             ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
228         return insertRawContact(RawContacts.CONTENT_URI, resolver, dbh, values);
229     }
230 
insertProfileRawContact( ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values)231     public static Uri insertProfileRawContact(
232             ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
233         return insertRawContact(Profile.CONTENT_RAW_CONTACTS_URI, resolver, dbh, values);
234     }
235 
insertRawContact(Uri tableUri, ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values)236     private static Uri insertRawContact(Uri tableUri,
237             ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
238         final SQLiteDatabase db = dbh.getWritableDatabase();
239 
240         final Uri rowUri = resolver.insert(tableUri, values);
241         Long timesContacted = values.getAsLong(RawContacts.LR_TIMES_CONTACTED);
242         if (timesContacted != null) {
243             // TIMES_CONTACTED is no longer modifiable via resolver, so we update the DB directly.
244             final long rid = Long.parseLong(rowUri.getLastPathSegment());
245 
246             final String[] args = {String.valueOf(rid)};
247 
248             db.update(Tables.RAW_CONTACTS,
249                     cv(RawContacts.RAW_TIMES_CONTACTED, (long) timesContacted),
250                     "_id=?", args);
251 
252             // Then propagate it to contacts too.
253             db.execSQL("UPDATE " + Tables.CONTACTS
254                     + " SET " + Contacts.RAW_TIMES_CONTACTED + " = ("
255                     + " SELECT sum(" + RawContacts.RAW_TIMES_CONTACTED + ") FROM "
256                     + Tables.RAW_CONTACTS + " AS r "
257                     + " WHERE " + Tables.CONTACTS + "._id = r." + RawContacts.CONTACT_ID
258                     + " GROUP BY r." + RawContacts.CONTACT_ID + ")");
259         }
260         return rowUri;
261     }
262 
executeSqlFromAssetFile( Context context, SQLiteDatabase db, String assetName)263     public static void executeSqlFromAssetFile(
264             Context context, SQLiteDatabase db, String assetName) {
265         try (InputStream input = context.getAssets().open(assetName);) {
266             BufferedReader r = new BufferedReader(new InputStreamReader(input));
267             String query;
268             while ((query = r.readLine()) != null) {
269                 if (query.trim().length() == 0 || query.startsWith("--")) {
270                     continue;
271                 }
272                 db.execSQL(query);
273             }
274         } catch (IOException e) {
275             throw new RuntimeException(e.toString());
276         }
277     }
278 }
279