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                 /* syncToMetadataNetwork =*/false);
250 
251         // We schedule a rescan if update(DIRECTORIES) is called while we're scanning all packages.
252         if (mDirectoriesForceUpdated) {
253             mDirectoriesForceUpdated = false;
254             mContactsProvider.scheduleRescanDirectories();
255         }
256 
257         return count;
258     }
259 
260     @VisibleForTesting
isDirectoryProvider(ProviderInfo provider)261     static boolean isDirectoryProvider(ProviderInfo provider) {
262         if (provider == null) return false;
263         Bundle metaData = provider.metaData;
264         if (metaData == null) return false;
265 
266         Object trueFalse = metaData.get(CONTACT_DIRECTORY_META_DATA);
267         return trueFalse != null && Boolean.TRUE.equals(trueFalse);
268     }
269 
270     @NonNull
getDirectoryProviderInfos(PackageManager pm)271     static private List<ProviderInfo> getDirectoryProviderInfos(PackageManager pm) {
272         return pm.queryContentProviders(null, 0, 0, CONTACT_DIRECTORY_META_DATA);
273     }
274 
275     /**
276      * @return List of packages that contain a directory provider.
277      */
278     @VisibleForTesting
279     @NonNull
getDirectoryProviderPackages(PackageManager pm)280     static Set<String> getDirectoryProviderPackages(PackageManager pm) {
281         final Set<String> ret = Sets.newHashSet();
282 
283         if (DEBUG) {
284             Log.d(TAG, "Listing directory provider packages...");
285         }
286 
287         for (ProviderInfo provider : getDirectoryProviderInfos(pm)) {
288             ret.add(provider.packageName);
289         }
290         if (DEBUG) {
291             Log.d(TAG, "Found " + ret.size() + " directory provider packages");
292         }
293 
294         return ret;
295     }
296 
scanAllPackages()297     private int scanAllPackages() {
298         SQLiteDatabase db = getDbHelper().getWritableDatabase();
299         insertDefaultDirectory(db);
300         insertLocalInvisibleDirectory(db);
301 
302         int count = 0;
303 
304         // Prepare query strings for removing stale rows which don't correspond to existing
305         // directories.
306         StringBuilder deleteWhereBuilder = new StringBuilder();
307         ArrayList<String> deleteWhereArgs = new ArrayList<String>();
308         deleteWhereBuilder.append("NOT (" + Directory._ID + "=? OR " + Directory._ID + "=?");
309         deleteWhereArgs.add(String.valueOf(Directory.DEFAULT));
310         deleteWhereArgs.add(String.valueOf(Directory.LOCAL_INVISIBLE));
311         final String wherePart = "(" + Directory.PACKAGE_NAME + "=? AND "
312                 + Directory.DIRECTORY_AUTHORITY + "=? AND "
313                 + Directory.ACCOUNT_NAME + "=? AND "
314                 + Directory.ACCOUNT_TYPE + "=?)";
315 
316         final Set<String> directoryProviderPackages = getDirectoryProviderPackages(mPackageManager);
317         for (String packageName : directoryProviderPackages) {
318             if (DEBUG) Log.d(TAG, "package=" + packageName);
319 
320             // getDirectoryProviderPackages() shouldn't return the contacts provider package
321             // because it doesn't have CONTACT_DIRECTORY_META_DATA, but just to make sure...
322             if (mContext.getPackageName().equals(packageName)) {
323                 Log.w(TAG, "  skipping self");
324                 continue;
325             }
326 
327             final PackageInfo packageInfo;
328             try {
329                 packageInfo = mPackageManager.getPackageInfo(packageName,
330                         PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
331                 if (packageInfo == null) continue;  // Just in case...
332             } catch (NameNotFoundException nnfe) {
333                 continue; // Application just removed?
334             }
335 
336             List<DirectoryInfo> directories = updateDirectoriesForPackage(packageInfo, true);
337             if (directories != null && !directories.isEmpty()) {
338                 count += directories.size();
339 
340                 // We shouldn't delete rows for existing directories.
341                 for (DirectoryInfo info : directories) {
342                     if (DEBUG) Log.d(TAG, "  directory=" + info);
343                     deleteWhereBuilder.append(" OR ");
344                     deleteWhereBuilder.append(wherePart);
345                     deleteWhereArgs.add(info.packageName);
346                     deleteWhereArgs.add(info.authority);
347                     deleteWhereArgs.add(info.accountName);
348                     deleteWhereArgs.add(info.accountType);
349                 }
350             }
351         }
352 
353         deleteWhereBuilder.append(")");  // Close "NOT ("
354 
355         int deletedRows = db.delete(Tables.DIRECTORIES, deleteWhereBuilder.toString(),
356                 deleteWhereArgs.toArray(new String[0]));
357 
358         saveKnownDirectoryProviders(directoryProviderPackages);
359 
360         Log.i(TAG, "deleted " + deletedRows
361                 + " stale rows which don't have any relevant directory");
362         return count;
363     }
364 
insertDefaultDirectory(SQLiteDatabase db)365     private void insertDefaultDirectory(SQLiteDatabase db) {
366         ContentValues values = new ContentValues();
367         values.put(Directory._ID, Directory.DEFAULT);
368         values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
369         values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
370         values.put(Directory.TYPE_RESOURCE_ID, R.string.default_directory);
371         values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
372                 mContext.getResources().getResourceName(R.string.default_directory));
373         values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
374         values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
375         values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
376         db.replace(Tables.DIRECTORIES, null, values);
377     }
378 
insertLocalInvisibleDirectory(SQLiteDatabase db)379     private void insertLocalInvisibleDirectory(SQLiteDatabase db) {
380         ContentValues values = new ContentValues();
381         values.put(Directory._ID, Directory.LOCAL_INVISIBLE);
382         values.put(Directory.PACKAGE_NAME, mContext.getPackageName());
383         values.put(Directory.DIRECTORY_AUTHORITY, ContactsContract.AUTHORITY);
384         values.put(Directory.TYPE_RESOURCE_ID, R.string.local_invisible_directory);
385         values.put(DirectoryColumns.TYPE_RESOURCE_NAME,
386                 mContext.getResources().getResourceName(R.string.local_invisible_directory));
387         values.put(Directory.EXPORT_SUPPORT, Directory.EXPORT_SUPPORT_NONE);
388         values.put(Directory.SHORTCUT_SUPPORT, Directory.SHORTCUT_SUPPORT_FULL);
389         values.put(Directory.PHOTO_SUPPORT, Directory.PHOTO_SUPPORT_FULL);
390         db.replace(Tables.DIRECTORIES, null, values);
391     }
392 
393     /**
394      * Scans the specified package for content directories.  The package may have
395      * already been removed, so packageName does not necessarily correspond to
396      * an installed package.
397      */
onPackageChanged(String packageName)398     public void onPackageChanged(String packageName) {
399         PackageInfo packageInfo = null;
400 
401         try {
402             packageInfo = mPackageManager.getPackageInfo(packageName,
403                     PackageManager.GET_PROVIDERS | PackageManager.GET_META_DATA);
404         } catch (NameNotFoundException e) {
405             // The package got removed
406             packageInfo = new PackageInfo();
407             packageInfo.packageName = packageName;
408         }
409 
410         if (mContext.getPackageName().equals(packageInfo.packageName)) {
411             if (DEBUG) Log.d(TAG, "Ignoring onPackageChanged for self");
412             return;
413         }
414         updateDirectoriesForPackage(packageInfo, false);
415     }
416 
417 
418     /**
419      * Scans the specified package for content directories and updates the {@link Directory}
420      * table accordingly.
421      */
updateDirectoriesForPackage( PackageInfo packageInfo, boolean initialScan)422     private List<DirectoryInfo> updateDirectoriesForPackage(
423             PackageInfo packageInfo, boolean initialScan) {
424         if (DEBUG) {
425             Log.d(TAG, "updateDirectoriesForPackage  packageName=" + packageInfo.packageName
426                     + " initialScan=" + initialScan);
427         }
428 
429         ArrayList<DirectoryInfo> directories = Lists.newArrayList();
430 
431         ProviderInfo[] providers = packageInfo.providers;
432         if (providers != null) {
433             for (ProviderInfo provider : providers) {
434                 if (isDirectoryProvider(provider)) {
435                     queryDirectoriesForAuthority(directories, provider);
436                 }
437             }
438         }
439 
440         if (directories.size() == 0 && initialScan) {
441             return null;
442         }
443 
444         SQLiteDatabase db = getDbHelper().getWritableDatabase();
445         db.beginTransaction();
446         try {
447             updateDirectories(db, directories);
448             // Clear out directories that are no longer present
449             StringBuilder sb = new StringBuilder(Directory.PACKAGE_NAME + "=?");
450             if (!directories.isEmpty()) {
451                 sb.append(" AND " + Directory._ID + " NOT IN(");
452                 for (DirectoryInfo info: directories) {
453                     sb.append(info.id).append(",");
454                 }
455                 sb.setLength(sb.length() - 1);  // Remove the extra comma
456                 sb.append(")");
457             }
458             final int numDeleted = db.delete(Tables.DIRECTORIES, sb.toString(),
459                     new String[] { packageInfo.packageName });
460             if (DEBUG) {
461                 Log.d(TAG, "  deleted " + numDeleted + " stale rows");
462             }
463             db.setTransactionSuccessful();
464         } finally {
465             db.endTransaction();
466         }
467 
468         mContactsProvider.resetDirectoryCache();
469         return directories;
470     }
471 
472     /**
473      * Sends a {@link Directory#CONTENT_URI} request to a specific contact directory
474      * provider and appends all discovered directories to the directoryInfo list.
475      */
queryDirectoriesForAuthority( ArrayList<DirectoryInfo> directoryInfo, ProviderInfo provider)476     protected void queryDirectoriesForAuthority(
477             ArrayList<DirectoryInfo> directoryInfo, ProviderInfo provider) {
478         Uri uri = new Uri.Builder().scheme("content")
479                 .authority(provider.authority).appendPath("directories").build();
480         Cursor cursor = null;
481         try {
482             cursor = mContext.getContentResolver().query(
483                     uri, DirectoryQuery.PROJECTION, null, null, null);
484             if (cursor == null) {
485                 Log.i(TAG, providerDescription(provider) + " returned a NULL cursor.");
486             } else {
487                 while (cursor.moveToNext()) {
488                     DirectoryInfo info = new DirectoryInfo();
489                     info.packageName = provider.packageName;
490                     info.authority = provider.authority;
491                     info.accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
492                     info.accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
493                     info.displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
494                     if (!cursor.isNull(DirectoryQuery.TYPE_RESOURCE_ID)) {
495                         info.typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
496                     }
497                     if (!cursor.isNull(DirectoryQuery.EXPORT_SUPPORT)) {
498                         int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
499                         switch (exportSupport) {
500                             case Directory.EXPORT_SUPPORT_NONE:
501                             case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY:
502                             case Directory.EXPORT_SUPPORT_ANY_ACCOUNT:
503                                 info.exportSupport = exportSupport;
504                                 break;
505                             default:
506                                 Log.e(TAG, providerDescription(provider)
507                                         + " - invalid export support flag: " + exportSupport);
508                         }
509                     }
510                     if (!cursor.isNull(DirectoryQuery.SHORTCUT_SUPPORT)) {
511                         int shortcutSupport = cursor.getInt(DirectoryQuery.SHORTCUT_SUPPORT);
512                         switch (shortcutSupport) {
513                             case Directory.SHORTCUT_SUPPORT_NONE:
514                             case Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY:
515                             case Directory.SHORTCUT_SUPPORT_FULL:
516                                 info.shortcutSupport = shortcutSupport;
517                                 break;
518                             default:
519                                 Log.e(TAG, providerDescription(provider)
520                                         + " - invalid shortcut support flag: " + shortcutSupport);
521                         }
522                     }
523                     if (!cursor.isNull(DirectoryQuery.PHOTO_SUPPORT)) {
524                         int photoSupport = cursor.getInt(DirectoryQuery.PHOTO_SUPPORT);
525                         switch (photoSupport) {
526                             case Directory.PHOTO_SUPPORT_NONE:
527                             case Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY:
528                             case Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY:
529                             case Directory.PHOTO_SUPPORT_FULL:
530                                 info.photoSupport = photoSupport;
531                                 break;
532                             default:
533                                 Log.e(TAG, providerDescription(provider)
534                                         + " - invalid photo support flag: " + photoSupport);
535                         }
536                     }
537                     directoryInfo.add(info);
538                 }
539             }
540         } catch (Throwable t) {
541             Log.e(TAG, providerDescription(provider) + " exception", t);
542         } finally {
543             if (cursor != null) {
544                 cursor.close();
545             }
546         }
547     }
548 
549     /**
550      * Updates the directories tables in the database to match the info received
551      * from directory providers.
552      */
updateDirectories(SQLiteDatabase db, ArrayList<DirectoryInfo> directoryInfo)553     private void updateDirectories(SQLiteDatabase db, ArrayList<DirectoryInfo> directoryInfo) {
554         // Insert or replace existing directories.
555         // This happens so infrequently that we can use a less-then-optimal one-a-time approach
556         for (DirectoryInfo info : directoryInfo) {
557             ContentValues values = new ContentValues();
558             values.put(Directory.PACKAGE_NAME, info.packageName);
559             values.put(Directory.DIRECTORY_AUTHORITY, info.authority);
560             values.put(Directory.ACCOUNT_NAME, info.accountName);
561             values.put(Directory.ACCOUNT_TYPE, info.accountType);
562             values.put(Directory.TYPE_RESOURCE_ID, info.typeResourceId);
563             values.put(Directory.DISPLAY_NAME, info.displayName);
564             values.put(Directory.EXPORT_SUPPORT, info.exportSupport);
565             values.put(Directory.SHORTCUT_SUPPORT, info.shortcutSupport);
566             values.put(Directory.PHOTO_SUPPORT, info.photoSupport);
567 
568             if (info.typeResourceId != 0) {
569                 String resourceName = getResourceNameById(info.packageName, info.typeResourceId);
570                 values.put(DirectoryColumns.TYPE_RESOURCE_NAME, resourceName);
571             }
572 
573             Cursor cursor = db.query(Tables.DIRECTORIES, new String[] { Directory._ID },
574                     Directory.PACKAGE_NAME + "=? AND " + Directory.DIRECTORY_AUTHORITY + "=? AND "
575                             + Directory.ACCOUNT_NAME + "=? AND " + Directory.ACCOUNT_TYPE + "=?",
576                     new String[] {
577                             info.packageName, info.authority, info.accountName, info.accountType },
578                     null, null, null);
579             try {
580                 long id;
581                 if (cursor.moveToFirst()) {
582                     id = cursor.getLong(0);
583                     db.update(Tables.DIRECTORIES, values, Directory._ID + "=?",
584                             new String[] { String.valueOf(id) });
585                 } else {
586                     id = db.insert(Tables.DIRECTORIES, null, values);
587                 }
588                 info.id = id;
589             } finally {
590                 cursor.close();
591             }
592         }
593     }
594 
providerDescription(ProviderInfo provider)595     protected String providerDescription(ProviderInfo provider) {
596         return "Directory provider " + provider.packageName + "(" + provider.authority + ")";
597     }
598 }
599