ContactDirectoryManagerTest.java revision 994c32a995a85937faf59e37a9fc88b526470d7d
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 17package com.android.providers.contacts; 18 19import com.android.providers.contacts.ContactsDatabaseHelper.AggregationExceptionColumns; 20import com.google.android.collect.Lists; 21 22import android.accounts.Account; 23import android.content.ContentValues; 24import android.content.Context; 25import android.content.pm.PackageInfo; 26import android.content.pm.ProviderInfo; 27import android.database.Cursor; 28import android.database.MatrixCursor; 29import android.net.Uri; 30import android.os.Bundle; 31import android.provider.ContactsContract; 32import android.provider.ContactsContract.AggregationExceptions; 33import android.provider.ContactsContract.Contacts; 34import android.provider.ContactsContract.Directory; 35import android.provider.ContactsContract.RawContacts; 36import android.test.mock.MockContentProvider; 37import android.test.suitebuilder.annotation.LargeTest; 38 39/** 40 * Unit tests for {@link ContactDirectoryManager}. Run the test like this: 41 * <code> 42 * adb shell am instrument -e class com.android.providers.contacts.ContactDirectoryManagerTest 43 * -w com.android.providers.contacts.tests/android.test.InstrumentationTestRunner 44 * </code> 45 */ 46@LargeTest 47public class ContactDirectoryManagerTest extends BaseContactsProvider2Test { 48 49 private ContactsMockPackageManager mPackageManager; 50 51 private ContactsProvider2 mProvider; 52 53 private ContactDirectoryManager mDirectoryManager; 54 55 public static class MockContactDirectoryProvider extends MockContentProvider { 56 57 private String mAuthority; 58 59 private MatrixCursor mResponse; 60 61 @Override 62 public void attachInfo(Context context, ProviderInfo info) { 63 mAuthority = info.authority; 64 } 65 66 public MatrixCursor createResponseCursor() { 67 mResponse = new MatrixCursor( 68 new String[] { Directory.ACCOUNT_NAME, Directory.ACCOUNT_TYPE, 69 Directory.DISPLAY_NAME, Directory.TYPE_RESOURCE_ID, 70 Directory.EXPORT_SUPPORT, Directory.SHORTCUT_SUPPORT, 71 Directory.PHOTO_SUPPORT }); 72 73 return mResponse; 74 } 75 76 @Override 77 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 78 String sortOrder) { 79 80 if (uri.toString().equals("content://" + mAuthority + "/directories")) { 81 return mResponse; 82 } else if (uri.toString().startsWith("content://" + mAuthority + "/contacts")) { 83 MatrixCursor cursor = new MatrixCursor( 84 new String[] { "projection", "selection", "selectionArgs", "sortOrder", 85 "accountName", "accountType"}); 86 cursor.addRow(new Object[] { 87 Lists.newArrayList(projection).toString(), 88 selection, 89 Lists.newArrayList(selectionArgs).toString(), 90 sortOrder, 91 uri.getQueryParameter(RawContacts.ACCOUNT_NAME), 92 uri.getQueryParameter(RawContacts.ACCOUNT_TYPE), 93 }); 94 return cursor; 95 } else if (uri.toString().startsWith( 96 "content://" + mAuthority + "/aggregation_exceptions")) { 97 return new MatrixCursor(projection); 98 } 99 100 fail("Unexpected uri: " + uri); 101 return null; 102 } 103 } 104 105 @Override 106 public void setUp() throws Exception { 107 super.setUp(); 108 109 mProvider = (ContactsProvider2) getProvider(); 110 mDirectoryManager = mProvider.getContactDirectoryManager(); 111 112 mPackageManager = (ContactsMockPackageManager) getProvider() 113 .getContext().getPackageManager(); 114 } 115 116 public void testScanAllProviders() throws Exception { 117 mPackageManager.setInstalledPackages( 118 Lists.newArrayList( 119 createProviderPackage("test.package1", "authority1"), 120 createProviderPackage("test.package2", "authority2"))); 121 122 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 123 MockContactDirectoryProvider.class, "authority1"); 124 125 MatrixCursor response1 = provider1.createResponseCursor(); 126 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 127 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 128 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY); 129 addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2, 130 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, 131 Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY); 132 133 MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider( 134 MockContactDirectoryProvider.class, "authority2"); 135 136 MatrixCursor response2 = provider2.createResponseCursor(); 137 addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3, 138 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL, 139 Directory.PHOTO_SUPPORT_FULL); 140 141 mDirectoryManager.scanAllPackages(); 142 143 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 144 assertEquals(5, cursor.getCount()); 145 146 cursor.moveToPosition(2); 147 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1", 148 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 149 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY); 150 151 cursor.moveToNext(); 152 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2", 153 "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT, 154 Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, Directory.PHOTO_SUPPORT_THUMBNAIL_ONLY); 155 156 cursor.moveToNext(); 157 assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3", 158 "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, 159 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL); 160 161 cursor.close(); 162 } 163 164 public void testPackageInstalled() throws Exception { 165 mPackageManager.setInstalledPackages( 166 Lists.newArrayList(createProviderPackage("test.package1", "authority1"))); 167 168 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 169 MockContactDirectoryProvider.class, "authority1"); 170 171 MatrixCursor response1 = provider1.createResponseCursor(); 172 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 173 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 174 Directory.PHOTO_SUPPORT_FULL); 175 176 mDirectoryManager.scanAllPackages(); 177 178 // At this point the manager has discovered a single directory (plus two 179 // standard ones). 180 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 181 assertEquals(3, cursor.getCount()); 182 cursor.close(); 183 184 // Pretend to install another package 185 MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider( 186 MockContactDirectoryProvider.class, "authority2"); 187 188 MatrixCursor response2 = provider2.createResponseCursor(); 189 addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3, 190 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL, 191 Directory.PHOTO_SUPPORT_FULL); 192 193 mPackageManager.getInstalledPackages(0).add( 194 createProviderPackage("test.package2", "authority2")); 195 196 mProvider.onPackageChanged("test.package2"); 197 198 cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 199 assertEquals(4, cursor.getCount()); 200 201 cursor.moveToPosition(2); 202 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1", 203 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 204 Directory.PHOTO_SUPPORT_FULL); 205 206 cursor.moveToNext(); 207 assertDirectoryRow(cursor, "test.package2", "authority2", "account-name3", "account-type3", 208 "display-name3", 3, Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, 209 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL); 210 211 cursor.close(); 212 } 213 214 public void testPackageUninstalled() throws Exception { 215 mPackageManager.setInstalledPackages( 216 Lists.newArrayList( 217 createProviderPackage("test.package1", "authority1"), 218 createProviderPackage("test.package2", "authority2"))); 219 220 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 221 MockContactDirectoryProvider.class, "authority1"); 222 223 MatrixCursor response1 = provider1.createResponseCursor(); 224 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 225 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 226 Directory.PHOTO_SUPPORT_NONE); 227 228 MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider( 229 MockContactDirectoryProvider.class, "authority2"); 230 231 MatrixCursor response2 = provider2.createResponseCursor(); 232 addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3, 233 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL, 234 Directory.PHOTO_SUPPORT_FULL); 235 236 mDirectoryManager.scanAllPackages(); 237 238 // At this point the manager has discovered two custom directories. 239 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 240 assertEquals(4, cursor.getCount()); 241 cursor.close(); 242 243 // Pretend to uninstall one of the packages 244 mPackageManager.getInstalledPackages(0).remove(1); 245 246 mProvider.onPackageChanged("test.package2"); 247 248 cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 249 assertEquals(3, cursor.getCount()); 250 251 cursor.moveToPosition(2); 252 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1", 253 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 254 Directory.PHOTO_SUPPORT_NONE); 255 256 cursor.close(); 257 } 258 259 public void testPackageReplaced() throws Exception { 260 mPackageManager.setInstalledPackages( 261 Lists.newArrayList( 262 createProviderPackage("test.package1", "authority1"), 263 createProviderPackage("test.package2", "authority2"))); 264 265 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 266 MockContactDirectoryProvider.class, "authority1"); 267 268 MatrixCursor response1 = provider1.createResponseCursor(); 269 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 270 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 271 Directory.PHOTO_SUPPORT_NONE); 272 273 MockContactDirectoryProvider provider2 = (MockContactDirectoryProvider) addProvider( 274 MockContactDirectoryProvider.class, "authority2"); 275 276 MatrixCursor response2 = provider2.createResponseCursor(); 277 addDirectoryRow(response2, "account-name3", "account-type3", "display-name3", 3, 278 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY, Directory.SHORTCUT_SUPPORT_FULL, 279 Directory.PHOTO_SUPPORT_FULL); 280 281 mDirectoryManager.scanAllPackages(); 282 283 // At this point the manager has discovered two custom directories. 284 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 285 assertEquals(4, cursor.getCount()); 286 cursor.close(); 287 288 // Pretend to replace the package with a different provider inside 289 MatrixCursor response3 = provider2.createResponseCursor(); 290 addDirectoryRow(response3, "account-name4", "account-type4", "display-name4", 4, 291 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 292 Directory.PHOTO_SUPPORT_NONE); 293 294 mProvider.onPackageChanged("test.package2"); 295 296 cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 297 assertEquals(4, cursor.getCount()); 298 299 cursor.moveToPosition(2); 300 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1", 301 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 302 Directory.PHOTO_SUPPORT_NONE); 303 304 cursor.moveToNext(); 305 assertDirectoryRow(cursor, "test.package2", "authority2", "account-name4", "account-type4", 306 "display-name4", 4, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 307 Directory.PHOTO_SUPPORT_NONE); 308 309 cursor.close(); 310 } 311 312 public void testAccountRemoval() throws Exception { 313 mPackageManager.setInstalledPackages( 314 Lists.newArrayList( 315 createProviderPackage("test.package1", "authority1"), 316 createProviderPackage("test.package2", "authority2"))); 317 318 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 319 MockContactDirectoryProvider.class, "authority1"); 320 321 ((ContactsProvider2)getProvider()).onAccountsUpdated( 322 new Account[]{ 323 new Account("account-name1", "account-type1"), 324 new Account("account-name2", "account-type2")}); 325 326 MatrixCursor response1 = provider1.createResponseCursor(); 327 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 328 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 329 Directory.PHOTO_SUPPORT_NONE); 330 addDirectoryRow(response1, "account-name2", "account-type2", "display-name2", 2, 331 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_DATA_ITEMS_ONLY, 332 Directory.PHOTO_SUPPORT_FULL_SIZE_ONLY); 333 334 mDirectoryManager.scanAllPackages(); 335 336 ((ContactsProvider2)getProvider()).onAccountsUpdated( 337 new Account[]{new Account("account-name1", "account-type1")}); 338 339 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 340 assertEquals(3, cursor.getCount()); 341 342 cursor.moveToPosition(2); 343 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name1", "account-type1", 344 "display-name1", 1, Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 345 Directory.PHOTO_SUPPORT_NONE); 346 347 cursor.close(); 348 } 349 350 public void testNotifyDirectoryChange() throws Exception { 351 mPackageManager.setInstalledPackages( 352 Lists.newArrayList(createProviderPackage("test.package1", "authority1"))); 353 354 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 355 MockContactDirectoryProvider.class, "authority1"); 356 357 MatrixCursor response1 = provider1.createResponseCursor(); 358 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 359 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 360 Directory.PHOTO_SUPPORT_NONE); 361 362 mDirectoryManager.scanAllPackages(); 363 364 // Pretend to replace the package with a different provider inside 365 MatrixCursor response2 = provider1.createResponseCursor(); 366 addDirectoryRow(response2, "account-name2", "account-type2", "display-name2", 2, 367 Directory.EXPORT_SUPPORT_ANY_ACCOUNT, Directory.SHORTCUT_SUPPORT_FULL, 368 Directory.PHOTO_SUPPORT_FULL); 369 370 ContactsContract.Directory.notifyDirectoryChange(mResolver); 371 372 Cursor cursor = mResolver.query(Directory.CONTENT_URI, null, null, null, null); 373 assertEquals(3, cursor.getCount()); 374 375 cursor.moveToPosition(2); 376 assertDirectoryRow(cursor, "test.package1", "authority1", "account-name2", "account-type2", 377 "display-name2", 2, Directory.EXPORT_SUPPORT_ANY_ACCOUNT, 378 Directory.SHORTCUT_SUPPORT_FULL, Directory.PHOTO_SUPPORT_FULL); 379 380 cursor.close(); 381 } 382 383 public void testForwardingToDirectoryProvider() throws Exception { 384 mPackageManager.setInstalledPackages( 385 Lists.newArrayList(createProviderPackage("test.package1", "authority1"))); 386 387 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 388 MockContactDirectoryProvider.class, "authority1"); 389 390 MatrixCursor response1 = provider1.createResponseCursor(); 391 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 392 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 393 Directory.PHOTO_SUPPORT_NONE); 394 395 mDirectoryManager.scanAllPackages(); 396 397 Cursor cursor = mResolver.query( 398 Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null); 399 cursor.moveToPosition(2); 400 long directoryId = cursor.getLong(0); 401 cursor.close(); 402 403 Uri contentUri = Contacts.CONTENT_URI.buildUpon().appendQueryParameter( 404 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build(); 405 406 // The request should be forwarded to TestProvider, which will simply 407 // package arguments and return them to us for verification 408 cursor = mResolver.query(contentUri, 409 new String[]{"f1", "f2"}, "query", new String[]{"s1", "s2"}, "so"); 410 assertNotNull(cursor); 411 assertEquals(1, cursor.getCount()); 412 cursor.moveToFirst(); 413 assertEquals("[f1, f2]", cursor.getString(cursor.getColumnIndex("projection"))); 414 assertEquals("query", cursor.getString(cursor.getColumnIndex("selection"))); 415 assertEquals("[s1, s2]", cursor.getString(cursor.getColumnIndex("selectionArgs"))); 416 assertEquals("so", cursor.getString(cursor.getColumnIndex("sortOrder"))); 417 assertEquals("account-name1", cursor.getString(cursor.getColumnIndex("accountName"))); 418 assertEquals("account-type1", cursor.getString(cursor.getColumnIndex("accountType"))); 419 cursor.close(); 420 } 421 422 public void testProjectionPopulated() throws Exception { 423 mPackageManager.setInstalledPackages( 424 Lists.newArrayList(createProviderPackage("test.package1", "authority1"))); 425 426 MockContactDirectoryProvider provider1 = (MockContactDirectoryProvider) addProvider( 427 MockContactDirectoryProvider.class, "authority1"); 428 429 MatrixCursor response1 = provider1.createResponseCursor(); 430 addDirectoryRow(response1, "account-name1", "account-type1", "display-name1", 1, 431 Directory.EXPORT_SUPPORT_NONE, Directory.SHORTCUT_SUPPORT_NONE, 432 Directory.PHOTO_SUPPORT_NONE); 433 434 mDirectoryManager.scanAllPackages(); 435 436 Cursor cursor = mResolver.query( 437 Directory.CONTENT_URI, new String[] { Directory._ID }, null, null, null); 438 cursor.moveToPosition(2); 439 long directoryId = cursor.getLong(0); 440 cursor.close(); 441 442 Uri contentUri = AggregationExceptions.CONTENT_URI.buildUpon().appendQueryParameter( 443 ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build(); 444 445 // The request should be forwarded to TestProvider, which will return an empty cursor 446 // but the projection should be correctly populated by ContactProvider 447 assertProjection(contentUri, new String[]{ 448 AggregationExceptionColumns._ID, 449 AggregationExceptions.TYPE, 450 AggregationExceptions.RAW_CONTACT_ID1, 451 AggregationExceptions.RAW_CONTACT_ID2, 452 }); 453 } 454 455 protected PackageInfo createProviderPackage(String packageName, String authority) { 456 PackageInfo providerPackage = new PackageInfo(); 457 providerPackage.packageName = packageName; 458 ProviderInfo providerInfo = new ProviderInfo(); 459 providerInfo.packageName = providerPackage.packageName; 460 providerInfo.authority = authority; 461 providerInfo.metaData = new Bundle(); 462 providerInfo.metaData.putBoolean("android.content.ContactDirectory", true); 463 providerPackage.providers = new ProviderInfo[] { providerInfo }; 464 return providerPackage; 465 } 466 467 protected void addDirectoryRow(MatrixCursor cursor, String accountName, String accountType, 468 String displayName, int typeResourceId, int exportSupport, int shortcutSupport, 469 int photoSupport) { 470 Object[] row = new Object[cursor.getColumnCount()]; 471 row[cursor.getColumnIndex(Directory.ACCOUNT_NAME)] = accountName; 472 row[cursor.getColumnIndex(Directory.ACCOUNT_TYPE)] = accountType; 473 row[cursor.getColumnIndex(Directory.DISPLAY_NAME)] = displayName; 474 row[cursor.getColumnIndex(Directory.TYPE_RESOURCE_ID)] = typeResourceId; 475 row[cursor.getColumnIndex(Directory.EXPORT_SUPPORT)] = exportSupport; 476 row[cursor.getColumnIndex(Directory.SHORTCUT_SUPPORT)] = shortcutSupport; 477 row[cursor.getColumnIndex(Directory.PHOTO_SUPPORT)] = photoSupport; 478 cursor.addRow(row); 479 } 480 481 protected void assertDirectoryRow(Cursor cursor, String packageName, String authority, 482 String accountName, String accountType, String displayName, int typeResourceId, 483 int exportSupport, int shortcutSupport, int photoSupport) { 484 ContentValues values = new ContentValues(); 485 values.put(Directory.PACKAGE_NAME, packageName); 486 values.put(Directory.DIRECTORY_AUTHORITY, authority); 487 values.put(Directory.ACCOUNT_NAME, accountName); 488 values.put(Directory.ACCOUNT_TYPE, accountType); 489 values.put(Directory.DISPLAY_NAME, displayName); 490 values.put(Directory.TYPE_RESOURCE_ID, typeResourceId); 491 values.put(Directory.EXPORT_SUPPORT, exportSupport); 492 values.put(Directory.SHORTCUT_SUPPORT, shortcutSupport); 493 values.put(Directory.PHOTO_SUPPORT, photoSupport); 494 495 assertCursorValues(cursor, values); 496 } 497} 498