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