1 /*
2  * Copyright (C) 2010 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.annotation.NonNull;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.pm.PackageInfo;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManager.NameNotFoundException;
25 import android.content.pm.ProviderInfo;
26 import android.content.res.Resources;
27 import android.content.res.Resources.NotFoundException;
28 import android.database.Cursor;
29 import android.database.sqlite.SQLiteDatabase;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.SystemClock;
33 import android.provider.ContactsContract;
34 import android.provider.ContactsContract.Directory;
35 import android.sysprop.ContactsProperties;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties;
40 import com.android.providers.contacts.ContactsDatabaseHelper.DirectoryColumns;
41 import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
42 import com.google.android.collect.Lists;
43 import com.google.android.collect.Sets;
44 import com.google.common.annotations.VisibleForTesting;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Objects;
49 import java.util.Set;
50 
51 /**
52  * Manages the contents of the {@link Directory} table.
53  */
54 public class ContactDirectoryManager {
55 
56     private static final String TAG = "ContactDirectoryManager";
57     private static final boolean DEBUG = AbstractContactsProvider.VERBOSE_LOGGING;
58 
59     public static final String CONTACT_DIRECTORY_META_DATA = "android.content.ContactDirectory";
60 
61     public static class DirectoryInfo {
62         long id;
63         String packageName;
64         String authority;
65         String accountName;
66         String accountType;
67         String displayName;
68         int typeResourceId;
69         int exportSupport = Directory.EXPORT_SUPPORT_NONE;
70         int shortcutSupport = Directory.SHORTCUT_SUPPORT_NONE;
71         int photoSupport = Directory.PHOTO_SUPPORT_NONE;
72         @Override
toString()73         public String toString() {
74             return "DirectoryInfo:"
75                     + "id=" + id
76                     + " packageName=" + accountType
77                     + " authority=" + authority
78                     + " accountName=***"
79                     + " accountType=" + accountType;
80         }
81     }
82 
83     private final static class DirectoryQuery {
84         public static final String[] PROJECTION = {
85             Directory.ACCOUNT_NAME,
86             Directory.ACCOUNT_TYPE,
87             Directory.DISPLAY_NAME,
88             Directory.TYPE_RESOURCE_ID,
89             Directory.EXPORT_SUPPORT,
90             Directory.SHORTCUT_SUPPORT,
91             Directory.PHOTO_SUPPORT,
92         };
93 
94         public static final int ACCOUNT_NAME = 0;
95         public static final int ACCOUNT_TYPE = 1;
96         public static final int DISPLAY_NAME = 2;
97         public static final int TYPE_RESOURCE_ID = 3;
98         public static final int EXPORT_SUPPORT = 4;
99         public static final int SHORTCUT_SUPPORT = 5;
100         public static final int PHOTO_SUPPORT = 6;
101     }
102 
103     private final ContactsProvider2 mContactsProvider;
104     private final Context mContext;
105     private final PackageManager mPackageManager;
106 
107     private volatile boolean mDirectoriesForceUpdated = false;
108 
ContactDirectoryManager(ContactsProvider2 contactsProvider)109     public ContactDirectoryManager(ContactsProvider2 contactsProvider) {
110         mContactsProvider = contactsProvider;
111         mContext = contactsProvider.getContext();
112         mPackageManager = mContext.getPackageManager();
113     }
114 
getDbHelper()115     public ContactsDatabaseHelper getDbHelper() {
116         return (ContactsDatabaseHelper) mContactsProvider.getDatabaseHelper();
117     }
118 
setDirectoriesForceUpdated(boolean updated)119     public void setDirectoriesForceUpdated(boolean updated) {
120         mDirectoriesForceUpdated = updated;
121     }
122 
123     /**
124      * Scans through existing directories to see if the cached resource IDs still
125      * match their original resource names.  If not - plays it safe by refreshing all directories.
126      *
127      * @return true if all resource IDs were found valid
128      */
areTypeResourceIdsValid()129     private boolean areTypeResourceIdsValid() {
130         SQLiteDatabase db = getDbHelper().getReadableDatabase();
131 
132         final Cursor cursor = db.rawQuery("SELECT DISTINCT "
133                 + Directory.TYPE_RESOURCE_ID + ","
134                 + Directory.PACKAGE_NAME + ","
135                 + DirectoryColumns.TYPE_RESOURCE_NAME
136                 + " FROM " + Tables.DIRECTORIES, null);
137         try {
138             while (cursor.moveToNext()) {
139                 int resourceId = cursor.getInt(0);
140                 if (resourceId != 0) {
141                     String packageName = cursor.getString(1);
142                     String storedResourceName = cursor.getString(2);
143                     String resourceName = getResourceNameById(packageName, resourceId);
144                     if (!TextUtils.equals(storedResourceName, resourceName)) {
145                         if (DEBUG) {
146                             Log.d(TAG, "areTypeResourceIdsValid:"
147                                     + " resourceId=" + resourceId
148                                     + " packageName=" + packageName
149                                     + " storedResourceName=" + storedResourceName
150                                     + " resourceName=" + resourceName);
151                         }
152                         return false;
153                     }
154                 }
155             }
156         } finally {
157             cursor.close();
158         }
159 
160         return true;
161     }
162 
163     /**
164      * Given a resource ID, returns the corresponding resource name or null if the package name /
165      * resource ID combination is invalid.
166      */
getResourceNameById(String packageName, int resourceId)167     private String getResourceNameById(String packageName, int resourceId) {
168         try {
169             Resources resources = mPackageManager.getResourcesForApplication(packageName);
170             return resources.getResourceName(resourceId);
171         } catch (NameNotFoundException e) {
172             return null;
173         } catch (NotFoundException e) {
174             return null;
175         }
176     }
177 
saveKnownDirectoryProviders(Set<String> packages)178     private void saveKnownDirectoryProviders(Set<String> packages) {
179         getDbHelper().setProperty(DbProperties.KNOWN_DIRECTORY_PACKAGES,
180                 TextUtils.join(",", packages));
181     }
182 
haveKnownDirectoryProvidersChanged(Set<String> packages)183     private boolean haveKnownDirectoryProvidersChanged(Set<String> packages) {
184         final String directoryPackages = TextUtils.join(",", packages);
185         final String prev = getDbHelper().getProperty(DbProperties.KNOWN_DIRECTORY_PACKAGES, "");
186 
187         final boolean changed = !Objects.equals(directoryPackages, prev);
188         if (DEBUG) {
189             Log.d(TAG, "haveKnownDirectoryProvidersChanged=" + changed + "\nprev=" + prev
190                     + " current=" + directoryPackages);
191         }
192         return changed;
193     }
194 
195     @VisibleForTesting
isRescanNeeded()196     boolean isRescanNeeded() {
197         if (ContactsProperties.debug_scan_all_packages().orElse(false)) {
198             Log.w(TAG, "debug.cp2.scan_all_packages set to 1.");
199             return true; // For debugging.
200         }
201         final String scanComplete =
202                 getDbHelper().getProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "0");
203         if (!"1".equals(scanComplete)) {
204             if (DEBUG) {
205                 Log.d(TAG, "DIRECTORY_SCAN_COMPLETE is 0.");
206             }
207             return true;
208         }
209         if (haveKnownDirectoryProvidersChanged(getDirectoryProviderPackages(mPackageManager))) {
210             Log.i(TAG, "Directory provider packages have changed.");
211             return true;
212         }
213         return false;
214     }
215 
216     /**
217      * Scans all packages for directory content providers.
218      */
scanAllPackages(boolean rescan)219     public int scanAllPackages(boolean rescan) {
220         if (!areTypeResourceIdsValid()) {
221             rescan = true;
222             Log.i(TAG, "!areTypeResourceIdsValid.");
223         }
224         if (rescan) {
225             getDbHelper().forceDirectoryRescan();
226         }
227 
228         return scanAllPackagesIfNeeded();
229     }
230 
scanAllPackagesIfNeeded()231     private int scanAllPackagesIfNeeded() {
232         if (!isRescanNeeded()) {
233             return 0;
234         }
235         if (DEBUG) {
236             Log.d(TAG, "scanAllPackagesIfNeeded()");
237         }
238         final long start = SystemClock.elapsedRealtime();
239         // Reset directory updated flag to false. If it's changed to true
240         // then we need to rescan directories.
241         mDirectoriesForceUpdated = false;
242         final int count = scanAllPackages();
243         getDbHelper().setProperty(DbProperties.DIRECTORY_SCAN_COMPLETE, "1");
244         final long end = SystemClock.elapsedRealtime();
245         Log.i(TAG, "Discovered " + count + " contact directories in " + (end - start) + "ms");
246 
247         // Announce the change to listeners of the contacts authority
248         mContactsProvider.notifyChange(/* syncToNetwork =*/false);
249 
250         // We schedule a rescan if update(DIRECTORIES) is called while we're scanning all packages.
251         if (mDirectoriesForceUpdated) {
252             mDirectoriesForceUpdated = false;
253             mContactsProvider.scheduleRescanDirectories();
254         }
255 
256         return count;
257     }
258 
259     @VisibleForTesting
isDirectoryProvider(ProviderInfo provider)260     static boolean isDirectoryProvider(ProviderInfo provider) {
261         if (provider == null) return false;
262         Bundle metaData = provider.metaData;
263         if (metaData == null) return false;
264 
265         Object trueFalse = metaData.get(CONTACT_DIRECTORY_META_DATA);
266         return trueFalse != null && Boolean.TRUE.equals(trueFalse);
267     }
268 
269     @NonNull
getDirectoryProviderInfos(PackageManager pm)270     static private List<ProviderInfo> getDirectoryProviderInfos(PackageManager pm) {
271         return pm.queryContentProviders(null, 0, 0, CONTACT_DIRECTORY_META_DATA);
272     }
273 
274     /**
275      * @return List of packages that contain a directory provider.
276      */
277     @VisibleForTesting
278     @NonNull
getDirectoryProviderPackages(PackageManager pm)279     static Set<String> getDirectoryProviderPackages(PackageManager pm) {
280         final Set<String> ret = Sets.newHashSet();
281 
282         if (DEBUG) {
283             Log.d(TAG, "Listing directory provider packages...");
284         }
285 
286         for (ProviderInfo provider : getDirectoryProviderInfos(pm)) {
287             ret.add(provider.packageName);
288         }
289         if (DEBUG) {
290             Log.d(TAG, "Found " + ret.size() + " directory provider packages");
291         }
292 
293         return ret;
294     }
295 
scanAllPackages()296     private int scanAllPackages() {
297         SQLiteDatabase db = getDbHelper().getWritableDatabase();
298         insertDefaultDirectory(db);
299         insertLocalInvisibleDirectory(db);
300 
301         int count = 0;
302 
303         // Prepare query strings for removing stale rows which don't correspond to existing
304         // directories.
305         StringBuilder deleteWhereBuilder = new StringBuilder();
306         ArrayList<String> deleteWhereArgs = new ArrayList<String>();
307         deleteWhereBuilder.append("NOT (" + Directory._ID + "=? OR " + Directory._ID + "=?");
308         deleteWhereArgs.add(String.valueOf(Directory.DEFAULT));
309         deleteWhereArgs.add(String.valueOf(Directory.LOCAL_INVISIBLE));
310         final String wherePart = "(" + Directory.PACKAGE_NAME + "=? AND "
311                 + Directory.DIRECTORY_AUTHORITY + "=? AND "
312                 + Directory.ACCOUNT_NAME + "=? AND "
313                 + Directory.ACCOUNT_TYPE + "=?)";
314 
315         final Set<String> directoryProviderPackages = getDirectoryProviderPackages(mPackageManager);
316         for (String packageName : directoryProviderPackages) {
317             if (DEBUG) Log.d(TAG, "package=" + packageName);
318 
319             // getDirectoryProviderPackages() shouldn't return the contacts provider package
320             // because it doesn't have CONTACT_DIRECTORY_META_DATA, but just to make sure...
321             if (mContext.getPackageName().equals(packageName)) {
322                 Log.w(TAG, "  skipping self");
323                 continue;
324             }
325 
326             final PackageInfo packageInfo;
327             try {
328                 packageInfo = mPackageManager.getPackageInfo(packageName,
329                         PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
330                 if (packageInfo == null) continue;  // Just in case...
331             } catch (NameNotFoundException nnfe) {
332                 continue; // Application just removed?
333             }
334 
335             List<DirectoryInfo> directories = updateDirectoriesForPackage(packageInfo, true);
336             if (directories != null && !directories.isEmpty()) {
337                 count += directories.size();
338 
339                 // We shouldn't delete rows for existing directories.
340                 for (DirectoryInfo info : directories) {
341                     if (DEBUG) Log.d(TAG, "  directory=" + info);
342                     deleteWhereBuilder.append(" OR ");
343                     deleteWhereBuilder.append(wherePart);
344                     deleteWhereArgs.add(info.packageName);
345                     deleteWhereArgs.add(info.authority);
346                     deleteWhereArgs.add(info.accountName);
347                     deleteWhereArgs.add(info.accountType);
348                 }
349             }
350         }
351 
352         deleteWhereBuilder.append(")");  // Close "NOT ("
353 
354         int deletedRows = db.delete(Tables.DIRECTORIES, deleteWhereBuilder.toString(),
355                 deleteWhereArgs.toArray(new String[0]));
356 
357         saveKnownDirectoryProviders(directoryProviderPackages);
358 
359         Log.i(TAG, "deleted " + deletedRows
360                 + " stale rows which don't have any relevant directory");
361         return count;
362     }
363 
insertDefaultDirectory(SQLiteDatabase db)364     private void insertDefaultDirectory(SQLiteDatabase db) {
365         ContentValues values = new ContentValues();
366         values.put(Directory._ID, Directory.DEFAULT);
367         values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
368         values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
369         values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
370         values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
371                 mContext.getResources().getResourceName(R.string.default_directory));
372         values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
373         values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
374         values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
375         db.replace(Tables.DIRECTORIES, null, values);
376     }
377 
insertLocalInvisibleDirectory(SQLiteDatabase db)378     private void insertLocalInvisibleDirectory(SQLiteDatabase db) {
379         ContentValues values = new ContentValues();
380         values.put(Directory._ID, Directory.LOCAL_INVISIBLE);
381         values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
382         values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
383         values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
384         values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
385                 mContext.getResources().getResourceName(R.string.local_invisible_directory));
386         values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
387         values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
388         values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
389         db.replace(Tables.DIRECTORIES, null, values);
390     }
391 
392     /**
393      * Scans the specified package for content directories.  The package may have
394      * already been removed, so packageName does not necessarily correspond to
395      * an installed package.
396      */
onPackageChanged(String packageName)397     public void onPackageChanged(String packageName) {
398         PackageInfo packageInfo = null;
399 
400         try {
401             packageInfo = mPackageManager.getPackageInfo(packageName,
402                     PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
403         } catch (NameNotFoundException e) {
404             // The package got removed
405             packageInfo = new PackageInfo();
406             packageInfo.packageName = packageName;
407         }
408 
409         if (mContext.getPackageName().equals(packageInfo.packageName)) {
410             if (DEBUG) Log.d(TAG, "Ignoring onPackageChanged for self");
411             return;
412         }
413         updateDirectoriesForPackage(packageInfo, false);
414     }
415 
416 
417     /**
418      * Scans the specified package for content directories and updates the {@link Directory}
419      * table accordingly.
420      */
updateDirectoriesForPackage( PackageInfo packageInfo, boolean initialScan)421     private List<DirectoryInfo> updateDirectoriesForPackage(
422             PackageInfo packageInfo, boolean initialScan) {
423         if (DEBUG) {
424             Log.d(TAG, "updateDirectoriesForPackage  packageName=" + packageInfo.packageName
425                     + " initialScan=" + initialScan);
426         }
427 
428         ArrayList<DirectoryInfo> directories = Lists.newArrayList();
429 
430         ProviderInfo[] providers = packageInfo.providers;
431         if (providers != null) {
432             for (ProviderInfo provider : providers) {
433                 if (isDirectoryProvider(provider)) {
434                     queryDirectoriesForAuthority(directories, provider);
435                 }
436             }
437         }
438 
439         if (directories.size() == 0 && initialScan) {
440             return null;
441         }
442 
443         SQLiteDatabase db = getDbHelper().getWritableDatabase();
444         db.beginTransaction();
445         try {
446             updateDirectories(db, directories);
447             // Clear out directories that are no longer present
448             StringBuilder sb = new StringBuilder(Directory.PACKAGE_NAME + "=?");
449             if (!directories.isEmpty()) {
450                 sb.append(" AND " + Directory._ID + " NOT IN(");
451                 for (DirectoryInfo info: directories) {
452                     sb.append(info.id).append(",");
453                 }
454                 sb.setLength(sb.length() - 1);  // Remove the extra comma
455                 sb.append(")");
456             }
457             final int numDeleted = db.delete(Tables.DIRECTORIES, sb.toString(),
458                     new String[] { packageInfo.packageName });
459             if (DEBUG) {
460                 Log.d(TAG, "  deleted " + numDeleted + " stale rows");
461             }
462             db.setTransactionSuccessful();
463         } finally {
464             db.endTransaction();
465         }
466 
467         mContactsProvider.resetDirectoryCache();
468         return directories;
469     }
470 
471     /**
472      * Sends a {@link Directory#CONTENT_URI} request to a specific contact directory
473      * provider and appends all discovered directories to the directoryInfo list.
474      */
queryDirectoriesForAuthority( ArrayList<DirectoryInfo> directoryInfo, ProviderInfo provider)475     protected void queryDirectoriesForAuthority(
476             ArrayList<DirectoryInfo> directoryInfo, ProviderInfo provider) {
477         Uri uri = new Uri.Builder().scheme("content")
478                 .authority(provider.authority).appendPath("directories").build();
479         Cursor cursor = null;
480         try {
481             cursor = mContext.getContentResolver().query(
482                     uri, DirectoryQuery.PROJECTION, null, null, null);
483             if (cursor == null) {
484                 Log.i(TAG, providerDescription(provider) + " returned a NULL cursor.");
485             } else {
486                 while (cursor.moveToNext()) {
487                     DirectoryInfo info = new DirectoryInfo();
488                     info.packageName = provider.packageName;
489                     info.authority = provider.authority;
490                     info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
491                     info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
492                     info.displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
493                     if (!cursor.isNull(DirectoryQuery.TYPE_RESOURCE_ID)) {
494                         info.typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
495                     }
496                     if (!cursor.isNull(DirectoryQuery.EXPORT_SUPPORT)) {
497                         int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
498                         switch (exportSupport) {
499                             case Directory.EXPORT_SUPPORT_NONE:
500                             case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY:
501                             case Directory.EXPORT_SUPPORT_ANY_ACCOUNT:
502                                 info.exportSupport = exportSupport;
503                                 break;
504                             default:
505                                 Log.e(TAG, providerDescription(provider)
506                                         + " - invalid export support flag: " + exportSupport);
507                         }
508                     }
509                     if (!cursor.isNull(DirectoryQuery.SHORTCUT_SUPPORT)) {
510                         int shortcutSupport = cursor.getInt(DirectoryQuery.SHORTCUT_SUPPORT);
511                         switch (shortcutSupport) {
512                             case Directory.SHORTCUT_SUPPORT_NONE:
513                             case Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY:
514                             case Directory.SHORTCUT_SUPPORT_FULL:
515                                 info.shortcutSupport = shortcutSupport;
516                                 break;
517                             default:
518                                 Log.e(TAG, providerDescription(provider)
519                                         + " - invalid shortcut support flag: " + shortcutSupport);
520                         }
521                     }
522                     if (!cursor.isNull(DirectoryQuery.PHOTO_SUPPORT)) {
523                         int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
524                         switch (photoSupport) {
525                             case Directory.PHOTO_SUPPORT_NONE:
526                             case Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY:
527                             case Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY:
528                             case Directory.PHOTO_SUPPORT_FULL:
529                                 info.photoSupport = photoSupport;
530                                 break;
531                             default:
532                                 Log.e(TAG, providerDescription(provider)
533                                         + " - invalid photo support flag: " + photoSupport);
534                         }
535                     }
536                     directoryInfo.add(info);
537                 }
538             }
539         } catch (Throwable t) {
540             Log.e(TAG, providerDescription(provider) + " exception", t);
541         } finally {
542             if (cursor != null) {
543                 cursor.close();
544             }
545         }
546     }
547 
548     /**
549      * Updates the directories tables in the database to match the info received
550      * from directory providers.
551      */
updateDirectories(SQLiteDatabase db, ArrayList<DirectoryInfo> directoryInfo)552     private void updateDirectories(SQLiteDatabase db, ArrayList<DirectoryInfo> directoryInfo) {
553         // Insert or replace existing directories.
554         // This happens so infrequently that we can use a less-then-optimal one-a-time approach
555         for (DirectoryInfo info : directoryInfo) {
556             ContentValues values = new ContentValues();
557             values.put(Directory.PACKAGE_NAME, info.packageName);
558             values.put(Directory.DIRECTORY_AUTHORITY, info.authority);
559             values.put(Directory.ACCOUNT_NAME, info.accountName);
560             values.put(Directory.ACCOUNT_TYPE, info.accountType);
561             values.put(Directory.TYPE_RESOURCE_ID, info.typeResourceId);
562             values.put(Directory.DISPLAY_NAME, info.displayName);
563             values.put(Directory.EXPORT_SUPPORT, info.exportSupport);
564             values.put(Directory.SHORTCUT_SUPPORT, info.shortcutSupport);
565             values.put(Directory.PHOTO_SUPPORT, info.photoSupport);
566 
567             if (info.typeResourceId != 0) {
568                 String resourceName = getResourceNameById(info.packageName, info.typeResourceId);
569                 values.put(DirectoryColumns.TYPE_RESOURCE_NAME, resourceName);
570             }
571 
572             Cursor cursor = db.query(Tables.DIRECTORIES, new String[] { Directory._ID },
573                     Directory.PACKAGE_NAME + "=? AND " + Directory.DIRECTORY_AUTHORITY + "=? AND "
574                             + Directory.ACCOUNT_NAME + "=? AND " + Directory.ACCOUNT_TYPE + "=?",
575                     new String[] {
576                             info.packageName, info.authority, info.accountName, info.accountType },
577                     null, null, null);
578             try {
579                 long id;
580                 if (cursor.moveToFirst()) {
581                     id = cursor.getLong(0);
582                     db.update(Tables.DIRECTORIES, values, Directory._ID + "=?",
583                             new String[] { String.valueOf(id) });
584                 } else {
585                     id = db.insert(Tables.DIRECTORIES, null, values);
586                 }
587                 info.id = id;
588             } finally {
589                 cursor.close();
590             }
591         }
592     }
593 
providerDescription(ProviderInfo provider)594     protected String providerDescription(ProviderInfo provider) {
595         return "Directory provider " + provider.packageName + "(" + provider.authority + ")";
596     }
597 }
598