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