android_os_Debug.cpp revision 3a8ce1bec819c9b104880493a6862fd2a9546132
1/* 2 * Copyright (C) 2007 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#define LOG_TAG "android.os.Debug" 18#include "JNIHelp.h" 19#include "jni.h" 20#include <utils/String8.h> 21#include "utils/misc.h" 22#include "cutils/debugger.h" 23 24#include <fcntl.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <string.h> 28#include <unistd.h> 29#include <time.h> 30#include <sys/time.h> 31#include <errno.h> 32#include <assert.h> 33#include <ctype.h> 34 35#ifdef HAVE_MALLOC_H 36#include <malloc.h> 37#endif 38 39namespace android 40{ 41 42enum { 43 HEAP_UNKNOWN, 44 HEAP_DALVIK, 45 HEAP_NATIVE, 46 HEAP_STACK, 47 HEAP_CURSOR, 48 HEAP_ASHMEM, 49 HEAP_UNKNOWN_DEV, 50 HEAP_SO, 51 HEAP_JAR, 52 HEAP_APK, 53 HEAP_TTF, 54 HEAP_DEX, 55 HEAP_OAT, 56 HEAP_ART, 57 HEAP_UNKNOWN_MAP, 58 59 _NUM_HEAP, 60 _NUM_CORE_HEAP = HEAP_NATIVE+1 61}; 62 63struct stat_fields { 64 jfieldID pss_field; 65 jfieldID privateDirty_field; 66 jfieldID sharedDirty_field; 67 jfieldID sharedClean_field; 68}; 69 70struct stat_field_names { 71 const char* pss_name; 72 const char* privateDirty_name; 73 const char* sharedDirty_name; 74 const char* sharedClean_name; 75}; 76 77static stat_fields stat_fields[_NUM_CORE_HEAP]; 78 79static stat_field_names stat_field_names[_NUM_CORE_HEAP] = { 80 { "otherPss", "otherPrivateDirty", "otherSharedDirty", "otherSharedClean" }, 81 { "dalvikPss", "dalvikPrivateDirty", "dalvikSharedDirty", "dalvikSharedClean" }, 82 { "nativePss", "nativePrivateDirty", "nativeSharedDirty", "nativeSharedClean" } 83}; 84 85jfieldID otherStats_field; 86 87struct stats_t { 88 int pss; 89 int privateDirty; 90 int sharedDirty; 91 int sharedClean; 92}; 93 94#define BINDER_STATS "/proc/binder/stats" 95 96static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz) 97{ 98#ifdef HAVE_MALLOC_H 99 struct mallinfo info = mallinfo(); 100 return (jlong) info.usmblks; 101#else 102 return -1; 103#endif 104} 105 106static jlong android_os_Debug_getNativeHeapAllocatedSize(JNIEnv *env, jobject clazz) 107{ 108#ifdef HAVE_MALLOC_H 109 struct mallinfo info = mallinfo(); 110 return (jlong) info.uordblks; 111#else 112 return -1; 113#endif 114} 115 116static jlong android_os_Debug_getNativeHeapFreeSize(JNIEnv *env, jobject clazz) 117{ 118#ifdef HAVE_MALLOC_H 119 struct mallinfo info = mallinfo(); 120 return (jlong) info.fordblks; 121#else 122 return -1; 123#endif 124} 125 126static void read_mapinfo(FILE *fp, stats_t* stats) 127{ 128 char line[1024]; 129 int len, nameLen; 130 bool skip, done = false; 131 132 unsigned size = 0, resident = 0, pss = 0; 133 unsigned shared_clean = 0, shared_dirty = 0; 134 unsigned private_clean = 0, private_dirty = 0; 135 unsigned referenced = 0; 136 unsigned temp; 137 138 unsigned long int start; 139 unsigned long int end = 0; 140 unsigned long int prevEnd = 0; 141 char* name; 142 int name_pos; 143 144 int whichHeap = HEAP_UNKNOWN; 145 int prevHeap = HEAP_UNKNOWN; 146 147 if(fgets(line, sizeof(line), fp) == 0) return; 148 149 while (!done) { 150 prevHeap = whichHeap; 151 prevEnd = end; 152 whichHeap = HEAP_UNKNOWN; 153 skip = false; 154 155 len = strlen(line); 156 if (len < 1) return; 157 line[--len] = 0; 158 159 if (sscanf(line, "%lx-%lx %*s %*x %*x:%*x %*d%n", &start, &end, &name_pos) != 2) { 160 skip = true; 161 } else { 162 while (isspace(line[name_pos])) { 163 name_pos += 1; 164 } 165 name = line + name_pos; 166 nameLen = strlen(name); 167 168 if ((strstr(name, "[heap]") == name) || 169 (strstr(name, "/dev/ashmem/libc malloc") == name)) { 170 whichHeap = HEAP_NATIVE; 171 } else if (strstr(name, "/dev/ashmem/dalvik-") == name) { 172 whichHeap = HEAP_DALVIK; 173 } else if (strstr(name, "[stack") == name) { 174 whichHeap = HEAP_STACK; 175 } else if (strstr(name, "/dev/ashmem/CursorWindow") == name) { 176 whichHeap = HEAP_CURSOR; 177 } else if (strstr(name, "/dev/ashmem/") == name) { 178 whichHeap = HEAP_ASHMEM; 179 } else if (strstr(name, "/dev/") == name) { 180 whichHeap = HEAP_UNKNOWN_DEV; 181 } else if (nameLen > 3 && strcmp(name+nameLen-3, ".so") == 0) { 182 whichHeap = HEAP_SO; 183 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".jar") == 0) { 184 whichHeap = HEAP_JAR; 185 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".apk") == 0) { 186 whichHeap = HEAP_APK; 187 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".ttf") == 0) { 188 whichHeap = HEAP_TTF; 189 } else if ((nameLen > 4 && strcmp(name+nameLen-4, ".dex") == 0) || 190 (nameLen > 5 && strcmp(name+nameLen-5, ".odex") == 0)) { 191 whichHeap = HEAP_DEX; 192 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".oat") == 0) { 193 whichHeap = HEAP_OAT; 194 } else if (nameLen > 4 && strcmp(name+nameLen-4, ".art") == 0) { 195 whichHeap = HEAP_ART; 196 } else if (nameLen > 0) { 197 whichHeap = HEAP_UNKNOWN_MAP; 198 } else if (start == prevEnd && prevHeap == HEAP_SO) { 199 // bss section of a shared library. 200 whichHeap = HEAP_SO; 201 } 202 } 203 204 //ALOGI("native=%d dalvik=%d sqlite=%d: %s\n", isNativeHeap, isDalvikHeap, 205 // isSqliteHeap, line); 206 207 while (true) { 208 if (fgets(line, 1024, fp) == 0) { 209 done = true; 210 break; 211 } 212 213 if (sscanf(line, "Size: %d kB", &temp) == 1) { 214 size = temp; 215 } else if (sscanf(line, "Rss: %d kB", &temp) == 1) { 216 resident = temp; 217 } else if (sscanf(line, "Pss: %d kB", &temp) == 1) { 218 pss = temp; 219 } else if (sscanf(line, "Shared_Clean: %d kB", &temp) == 1) { 220 shared_clean = temp; 221 } else if (sscanf(line, "Shared_Dirty: %d kB", &temp) == 1) { 222 shared_dirty = temp; 223 } else if (sscanf(line, "Private_Clean: %d kB", &temp) == 1) { 224 private_clean = temp; 225 } else if (sscanf(line, "Private_Dirty: %d kB", &temp) == 1) { 226 private_dirty = temp; 227 } else if (sscanf(line, "Referenced: %d kB", &temp) == 1) { 228 referenced = temp; 229 } else if (strlen(line) > 30 && line[8] == '-' && line[17] == ' ') { 230 // looks like a new mapping 231 // example: "10000000-10001000 ---p 10000000 00:00 0" 232 break; 233 } 234 } 235 236 if (!skip) { 237 stats[whichHeap].pss += pss; 238 stats[whichHeap].privateDirty += private_dirty; 239 stats[whichHeap].sharedDirty += shared_dirty; 240 stats[whichHeap].sharedClean += shared_clean; 241 } 242 } 243} 244 245static void load_maps(int pid, stats_t* stats) 246{ 247 char tmp[128]; 248 FILE *fp; 249 250 sprintf(tmp, "/proc/%d/smaps", pid); 251 fp = fopen(tmp, "r"); 252 if (fp == 0) return; 253 254 read_mapinfo(fp, stats); 255 fclose(fp); 256} 257 258static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, 259 jint pid, jobject object) 260{ 261 stats_t stats[_NUM_HEAP]; 262 memset(&stats, 0, sizeof(stats)); 263 264 load_maps(pid, stats); 265 266 for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) { 267 stats[HEAP_UNKNOWN].pss += stats[i].pss; 268 stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty; 269 stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty; 270 stats[HEAP_UNKNOWN].sharedClean += stats[i].sharedClean; 271 } 272 273 for (int i=0; i<_NUM_CORE_HEAP; i++) { 274 env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss); 275 env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty); 276 env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty); 277 env->SetIntField(object, stat_fields[i].sharedClean_field, stats[i].sharedClean); 278 } 279 280 jintArray otherIntArray = (jintArray)env->GetObjectField(object, otherStats_field); 281 282 jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0); 283 if (otherArray == NULL) { 284 return; 285 } 286 287 int j=0; 288 for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) { 289 otherArray[j++] = stats[i].pss; 290 otherArray[j++] = stats[i].privateDirty; 291 otherArray[j++] = stats[i].sharedDirty; 292 otherArray[j++] = stats[i].sharedClean; 293 } 294 295 env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0); 296} 297 298static void android_os_Debug_getDirtyPages(JNIEnv *env, jobject clazz, jobject object) 299{ 300 android_os_Debug_getDirtyPagesPid(env, clazz, getpid(), object); 301} 302 303static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid) 304{ 305 char line[1024]; 306 jlong pss = 0; 307 unsigned temp; 308 309 char tmp[128]; 310 FILE *fp; 311 312 sprintf(tmp, "/proc/%d/smaps", pid); 313 fp = fopen(tmp, "r"); 314 if (fp == 0) return 0; 315 316 while (true) { 317 if (fgets(line, 1024, fp) == 0) { 318 break; 319 } 320 321 if (sscanf(line, "Pss: %d kB", &temp) == 1) { 322 pss += temp; 323 } 324 } 325 326 fclose(fp); 327 328 return pss; 329} 330 331static jlong android_os_Debug_getPss(JNIEnv *env, jobject clazz) 332{ 333 return android_os_Debug_getPssPid(env, clazz, getpid()); 334} 335 336static jint read_binder_stat(const char* stat) 337{ 338 FILE* fp = fopen(BINDER_STATS, "r"); 339 if (fp == NULL) { 340 return -1; 341 } 342 343 char line[1024]; 344 345 char compare[128]; 346 int len = snprintf(compare, 128, "proc %d", getpid()); 347 348 // loop until we have the block that represents this process 349 do { 350 if (fgets(line, 1024, fp) == 0) { 351 return -1; 352 } 353 } while (strncmp(compare, line, len)); 354 355 // now that we have this process, read until we find the stat that we are looking for 356 len = snprintf(compare, 128, " %s: ", stat); 357 358 do { 359 if (fgets(line, 1024, fp) == 0) { 360 return -1; 361 } 362 } while (strncmp(compare, line, len)); 363 364 // we have the line, now increment the line ptr to the value 365 char* ptr = line + len; 366 return atoi(ptr); 367} 368 369static jint android_os_Debug_getBinderSentTransactions(JNIEnv *env, jobject clazz) 370{ 371 return read_binder_stat("bcTRANSACTION"); 372} 373 374static jint android_os_getBinderReceivedTransactions(JNIEnv *env, jobject clazz) 375{ 376 return read_binder_stat("brTRANSACTION"); 377} 378 379// these are implemented in android_util_Binder.cpp 380jint android_os_Debug_getLocalObjectCount(JNIEnv* env, jobject clazz); 381jint android_os_Debug_getProxyObjectCount(JNIEnv* env, jobject clazz); 382jint android_os_Debug_getDeathObjectCount(JNIEnv* env, jobject clazz); 383 384 385/* pulled out of bionic */ 386extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, 387 size_t* infoSize, size_t* totalMemory, size_t* backtraceSize); 388extern "C" void free_malloc_leak_info(uint8_t* info); 389#define SIZE_FLAG_ZYGOTE_CHILD (1<<31) 390#define BACKTRACE_SIZE 32 391 392/* 393 * This is a qsort() callback. 394 * 395 * See dumpNativeHeap() for comments about the data format and sort order. 396 */ 397static int compareHeapRecords(const void* vrec1, const void* vrec2) 398{ 399 const size_t* rec1 = (const size_t*) vrec1; 400 const size_t* rec2 = (const size_t*) vrec2; 401 size_t size1 = *rec1; 402 size_t size2 = *rec2; 403 404 if (size1 < size2) { 405 return 1; 406 } else if (size1 > size2) { 407 return -1; 408 } 409 410 intptr_t* bt1 = (intptr_t*)(rec1 + 2); 411 intptr_t* bt2 = (intptr_t*)(rec2 + 2); 412 for (size_t idx = 0; idx < BACKTRACE_SIZE; idx++) { 413 intptr_t addr1 = bt1[idx]; 414 intptr_t addr2 = bt2[idx]; 415 if (addr1 == addr2) { 416 if (addr1 == 0) 417 break; 418 continue; 419 } 420 if (addr1 < addr2) { 421 return -1; 422 } else if (addr1 > addr2) { 423 return 1; 424 } 425 } 426 427 return 0; 428} 429 430/* 431 * The get_malloc_leak_info() call returns an array of structs that 432 * look like this: 433 * 434 * size_t size 435 * size_t allocations 436 * intptr_t backtrace[32] 437 * 438 * "size" is the size of the allocation, "backtrace" is a fixed-size 439 * array of function pointers, and "allocations" is the number of 440 * allocations with the exact same size and backtrace. 441 * 442 * The entries are sorted by descending total size (i.e. size*allocations) 443 * then allocation count. For best results with "diff" we'd like to sort 444 * primarily by individual size then stack trace. Since the entries are 445 * fixed-size, and we're allowed (by the current implementation) to mangle 446 * them, we can do this in place. 447 */ 448static void dumpNativeHeap(FILE* fp) 449{ 450 uint8_t* info = NULL; 451 size_t overallSize, infoSize, totalMemory, backtraceSize; 452 453 get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, 454 &backtraceSize); 455 if (info == NULL) { 456 fprintf(fp, "Native heap dump not available. To enable, run these" 457 " commands (requires root):\n"); 458 fprintf(fp, "$ adb shell setprop libc.debug.malloc 1\n"); 459 fprintf(fp, "$ adb shell stop\n"); 460 fprintf(fp, "$ adb shell start\n"); 461 return; 462 } 463 assert(infoSize != 0); 464 assert(overallSize % infoSize == 0); 465 466 fprintf(fp, "Android Native Heap Dump v1.0\n\n"); 467 468 size_t recordCount = overallSize / infoSize; 469 fprintf(fp, "Total memory: %zu\n", totalMemory); 470 fprintf(fp, "Allocation records: %zd\n", recordCount); 471 if (backtraceSize != BACKTRACE_SIZE) { 472 fprintf(fp, "WARNING: mismatched backtrace sizes (%d vs. %d)\n", 473 backtraceSize, BACKTRACE_SIZE); 474 } 475 fprintf(fp, "\n"); 476 477 /* re-sort the entries */ 478 qsort(info, recordCount, infoSize, compareHeapRecords); 479 480 /* dump the entries to the file */ 481 const uint8_t* ptr = info; 482 for (size_t idx = 0; idx < recordCount; idx++) { 483 size_t size = *(size_t*) ptr; 484 size_t allocations = *(size_t*) (ptr + sizeof(size_t)); 485 intptr_t* backtrace = (intptr_t*) (ptr + sizeof(size_t) * 2); 486 487 fprintf(fp, "z %d sz %8zu num %4zu bt", 488 (size & SIZE_FLAG_ZYGOTE_CHILD) != 0, 489 size & ~SIZE_FLAG_ZYGOTE_CHILD, 490 allocations); 491 for (size_t bt = 0; bt < backtraceSize; bt++) { 492 if (backtrace[bt] == 0) { 493 break; 494 } else { 495 fprintf(fp, " %08x", backtrace[bt]); 496 } 497 } 498 fprintf(fp, "\n"); 499 500 ptr += infoSize; 501 } 502 503 free_malloc_leak_info(info); 504 505 fprintf(fp, "MAPS\n"); 506 const char* maps = "/proc/self/maps"; 507 FILE* in = fopen(maps, "r"); 508 if (in == NULL) { 509 fprintf(fp, "Could not open %s\n", maps); 510 return; 511 } 512 char buf[BUFSIZ]; 513 while (size_t n = fread(buf, sizeof(char), BUFSIZ, in)) { 514 fwrite(buf, sizeof(char), n, fp); 515 } 516 fclose(in); 517 518 fprintf(fp, "END\n"); 519} 520 521/* 522 * Dump the native heap, writing human-readable output to the specified 523 * file descriptor. 524 */ 525static void android_os_Debug_dumpNativeHeap(JNIEnv* env, jobject clazz, 526 jobject fileDescriptor) 527{ 528 if (fileDescriptor == NULL) { 529 jniThrowNullPointerException(env, "fd == null"); 530 return; 531 } 532 int origFd = jniGetFDFromFileDescriptor(env, fileDescriptor); 533 if (origFd < 0) { 534 jniThrowRuntimeException(env, "Invalid file descriptor"); 535 return; 536 } 537 538 /* dup() the descriptor so we don't close the original with fclose() */ 539 int fd = dup(origFd); 540 if (fd < 0) { 541 ALOGW("dup(%d) failed: %s\n", origFd, strerror(errno)); 542 jniThrowRuntimeException(env, "dup() failed"); 543 return; 544 } 545 546 FILE* fp = fdopen(fd, "w"); 547 if (fp == NULL) { 548 ALOGW("fdopen(%d) failed: %s\n", fd, strerror(errno)); 549 close(fd); 550 jniThrowRuntimeException(env, "fdopen() failed"); 551 return; 552 } 553 554 ALOGD("Native heap dump starting...\n"); 555 dumpNativeHeap(fp); 556 ALOGD("Native heap dump complete.\n"); 557 558 fclose(fp); 559} 560 561 562static void android_os_Debug_dumpNativeBacktraceToFile(JNIEnv* env, jobject clazz, 563 jint pid, jstring fileName) 564{ 565 if (fileName == NULL) { 566 jniThrowNullPointerException(env, "file == null"); 567 return; 568 } 569 const jchar* str = env->GetStringCritical(fileName, 0); 570 String8 fileName8; 571 if (str) { 572 fileName8 = String8(str, env->GetStringLength(fileName)); 573 env->ReleaseStringCritical(fileName, str); 574 } 575 576 int fd = open(fileName8.string(), O_CREAT | O_WRONLY | O_NOFOLLOW, 0666); /* -rw-rw-rw- */ 577 if (fd < 0) { 578 fprintf(stderr, "Can't open %s: %s\n", fileName8.string(), strerror(errno)); 579 return; 580 } 581 582 if (lseek(fd, 0, SEEK_END) < 0) { 583 fprintf(stderr, "lseek: %s\n", strerror(errno)); 584 } else { 585 dump_backtrace_to_file(pid, fd); 586 } 587 588 close(fd); 589} 590 591/* 592 * JNI registration. 593 */ 594 595static JNINativeMethod gMethods[] = { 596 { "getNativeHeapSize", "()J", 597 (void*) android_os_Debug_getNativeHeapSize }, 598 { "getNativeHeapAllocatedSize", "()J", 599 (void*) android_os_Debug_getNativeHeapAllocatedSize }, 600 { "getNativeHeapFreeSize", "()J", 601 (void*) android_os_Debug_getNativeHeapFreeSize }, 602 { "getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V", 603 (void*) android_os_Debug_getDirtyPages }, 604 { "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)V", 605 (void*) android_os_Debug_getDirtyPagesPid }, 606 { "getPss", "()J", 607 (void*) android_os_Debug_getPss }, 608 { "getPss", "(I)J", 609 (void*) android_os_Debug_getPssPid }, 610 { "dumpNativeHeap", "(Ljava/io/FileDescriptor;)V", 611 (void*) android_os_Debug_dumpNativeHeap }, 612 { "getBinderSentTransactions", "()I", 613 (void*) android_os_Debug_getBinderSentTransactions }, 614 { "getBinderReceivedTransactions", "()I", 615 (void*) android_os_getBinderReceivedTransactions }, 616 { "getBinderLocalObjectCount", "()I", 617 (void*)android_os_Debug_getLocalObjectCount }, 618 { "getBinderProxyObjectCount", "()I", 619 (void*)android_os_Debug_getProxyObjectCount }, 620 { "getBinderDeathObjectCount", "()I", 621 (void*)android_os_Debug_getDeathObjectCount }, 622 { "dumpNativeBacktraceToFile", "(ILjava/lang/String;)V", 623 (void*)android_os_Debug_dumpNativeBacktraceToFile }, 624}; 625 626int register_android_os_Debug(JNIEnv *env) 627{ 628 jclass clazz = env->FindClass("android/os/Debug$MemoryInfo"); 629 630 // Sanity check the number of other statistics expected in Java matches here. 631 jfieldID numOtherStats_field = env->GetStaticFieldID(clazz, "NUM_OTHER_STATS", "I"); 632 jint numOtherStats = env->GetStaticIntField(clazz, numOtherStats_field); 633 int expectedNumOtherStats = _NUM_HEAP - _NUM_CORE_HEAP; 634 if (numOtherStats != expectedNumOtherStats) { 635 jniThrowExceptionFmt(env, "java/lang/RuntimeException", 636 "android.os.Debug.Meminfo.NUM_OTHER_STATS=%d expected %d", 637 numOtherStats, expectedNumOtherStats); 638 return JNI_ERR; 639 } 640 641 otherStats_field = env->GetFieldID(clazz, "otherStats", "[I"); 642 643 for (int i=0; i<_NUM_CORE_HEAP; i++) { 644 stat_fields[i].pss_field = 645 env->GetFieldID(clazz, stat_field_names[i].pss_name, "I"); 646 stat_fields[i].privateDirty_field = 647 env->GetFieldID(clazz, stat_field_names[i].privateDirty_name, "I"); 648 stat_fields[i].sharedDirty_field = 649 env->GetFieldID(clazz, stat_field_names[i].sharedDirty_name, "I"); 650 stat_fields[i].sharedClean_field = 651 env->GetFieldID(clazz, stat_field_names[i].sharedClean_name, "I"); 652 } 653 654 return jniRegisterNativeMethods(env, "android/os/Debug", gMethods, NELEM(gMethods)); 655} 656 657}; // namespace android 658