OSDN Git Service

Fix build am: 48caffe6fe am: 1da4542abd -s ours
[android-x86/frameworks-av.git] / camera / CameraMetadata.cpp
1 /*
2  * Copyright (C) 2012 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_NDEBUG 0
18
19 #define LOG_TAG "Camera2-Metadata"
20 #include <utils/Log.h>
21 #include <utils/Errors.h>
22
23 #include <camera/CameraMetadata.h>
24 #include <binder/Parcel.h>
25
26 namespace android {
27
28 #define ALIGN_TO(val, alignment) \
29     (((uintptr_t)(val) + ((alignment) - 1)) & ~((alignment) - 1))
30
31 typedef Parcel::WritableBlob WritableBlob;
32 typedef Parcel::ReadableBlob ReadableBlob;
33
34 CameraMetadata::CameraMetadata() :
35         mBuffer(NULL), mLocked(false) {
36 }
37
38 CameraMetadata::CameraMetadata(size_t entryCapacity, size_t dataCapacity) :
39         mLocked(false)
40 {
41     mBuffer = allocate_camera_metadata(entryCapacity, dataCapacity);
42 }
43
44 CameraMetadata::CameraMetadata(const CameraMetadata &other) :
45         mLocked(false) {
46     mBuffer = clone_camera_metadata(other.mBuffer);
47 }
48
49 CameraMetadata::CameraMetadata(camera_metadata_t *buffer) :
50         mBuffer(NULL), mLocked(false) {
51     acquire(buffer);
52 }
53
54 CameraMetadata &CameraMetadata::operator=(const CameraMetadata &other) {
55     return operator=(other.mBuffer);
56 }
57
58 CameraMetadata &CameraMetadata::operator=(const camera_metadata_t *buffer) {
59     if (mLocked) {
60         ALOGE("%s: Assignment to a locked CameraMetadata!", __FUNCTION__);
61         return *this;
62     }
63
64     if (CC_LIKELY(buffer != mBuffer)) {
65         camera_metadata_t *newBuffer = clone_camera_metadata(buffer);
66         clear();
67         mBuffer = newBuffer;
68     }
69     return *this;
70 }
71
72 CameraMetadata::~CameraMetadata() {
73     mLocked = false;
74     clear();
75 }
76
77 const camera_metadata_t* CameraMetadata::getAndLock() const {
78     mLocked = true;
79     return mBuffer;
80 }
81
82 status_t CameraMetadata::unlock(const camera_metadata_t *buffer) {
83     if (!mLocked) {
84         ALOGE("%s: Can't unlock a non-locked CameraMetadata!", __FUNCTION__);
85         return INVALID_OPERATION;
86     }
87     if (buffer != mBuffer) {
88         ALOGE("%s: Can't unlock CameraMetadata with wrong pointer!",
89                 __FUNCTION__);
90         return BAD_VALUE;
91     }
92     mLocked = false;
93     return OK;
94 }
95
96 camera_metadata_t* CameraMetadata::release() {
97     if (mLocked) {
98         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
99         return NULL;
100     }
101     camera_metadata_t *released = mBuffer;
102     mBuffer = NULL;
103     return released;
104 }
105
106 void CameraMetadata::clear() {
107     if (mLocked) {
108         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
109         return;
110     }
111     if (mBuffer) {
112         free_camera_metadata(mBuffer);
113         mBuffer = NULL;
114     }
115 }
116
117 void CameraMetadata::acquire(camera_metadata_t *buffer) {
118     if (mLocked) {
119         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
120         return;
121     }
122     clear();
123     mBuffer = buffer;
124
125     ALOGE_IF(validate_camera_metadata_structure(mBuffer, /*size*/NULL) != OK,
126              "%s: Failed to validate metadata structure %p",
127              __FUNCTION__, buffer);
128 }
129
130 void CameraMetadata::acquire(CameraMetadata &other) {
131     if (mLocked) {
132         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
133         return;
134     }
135     acquire(other.release());
136 }
137
138 status_t CameraMetadata::append(const CameraMetadata &other) {
139     return append(other.mBuffer);
140 }
141
142 status_t CameraMetadata::append(const camera_metadata_t* other) {
143     if (mLocked) {
144         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
145         return INVALID_OPERATION;
146     }
147     size_t extraEntries = get_camera_metadata_entry_count(other);
148     size_t extraData = get_camera_metadata_data_count(other);
149     resizeIfNeeded(extraEntries, extraData);
150
151     return append_camera_metadata(mBuffer, other);
152 }
153
154 size_t CameraMetadata::entryCount() const {
155     return (mBuffer == NULL) ? 0 :
156             get_camera_metadata_entry_count(mBuffer);
157 }
158
159 bool CameraMetadata::isEmpty() const {
160     return entryCount() == 0;
161 }
162
163 status_t CameraMetadata::sort() {
164     if (mLocked) {
165         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
166         return INVALID_OPERATION;
167     }
168     return sort_camera_metadata(mBuffer);
169 }
170
171 status_t CameraMetadata::checkType(uint32_t tag, uint8_t expectedType) {
172     int tagType = get_camera_metadata_tag_type(tag);
173     if ( CC_UNLIKELY(tagType == -1)) {
174         ALOGE("Update metadata entry: Unknown tag %d", tag);
175         return INVALID_OPERATION;
176     }
177     if ( CC_UNLIKELY(tagType != expectedType) ) {
178         ALOGE("Mismatched tag type when updating entry %s (%d) of type %s; "
179                 "got type %s data instead ",
180                 get_camera_metadata_tag_name(tag), tag,
181                 camera_metadata_type_names[tagType],
182                 camera_metadata_type_names[expectedType]);
183         return INVALID_OPERATION;
184     }
185     return OK;
186 }
187
188 status_t CameraMetadata::update(uint32_t tag,
189         const int32_t *data, size_t data_count) {
190     status_t res;
191     if (mLocked) {
192         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
193         return INVALID_OPERATION;
194     }
195     if ( (res = checkType(tag, TYPE_INT32)) != OK) {
196         return res;
197     }
198     return updateImpl(tag, (const void*)data, data_count);
199 }
200
201 status_t CameraMetadata::update(uint32_t tag,
202         const uint8_t *data, size_t data_count) {
203     status_t res;
204     if (mLocked) {
205         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
206         return INVALID_OPERATION;
207     }
208     if ( (res = checkType(tag, TYPE_BYTE)) != OK) {
209         return res;
210     }
211     return updateImpl(tag, (const void*)data, data_count);
212 }
213
214 status_t CameraMetadata::update(uint32_t tag,
215         const float *data, size_t data_count) {
216     status_t res;
217     if (mLocked) {
218         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
219         return INVALID_OPERATION;
220     }
221     if ( (res = checkType(tag, TYPE_FLOAT)) != OK) {
222         return res;
223     }
224     return updateImpl(tag, (const void*)data, data_count);
225 }
226
227 status_t CameraMetadata::update(uint32_t tag,
228         const int64_t *data, size_t data_count) {
229     status_t res;
230     if (mLocked) {
231         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
232         return INVALID_OPERATION;
233     }
234     if ( (res = checkType(tag, TYPE_INT64)) != OK) {
235         return res;
236     }
237     return updateImpl(tag, (const void*)data, data_count);
238 }
239
240 status_t CameraMetadata::update(uint32_t tag,
241         const double *data, size_t data_count) {
242     status_t res;
243     if (mLocked) {
244         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
245         return INVALID_OPERATION;
246     }
247     if ( (res = checkType(tag, TYPE_DOUBLE)) != OK) {
248         return res;
249     }
250     return updateImpl(tag, (const void*)data, data_count);
251 }
252
253 status_t CameraMetadata::update(uint32_t tag,
254         const camera_metadata_rational_t *data, size_t data_count) {
255     status_t res;
256     if (mLocked) {
257         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
258         return INVALID_OPERATION;
259     }
260     if ( (res = checkType(tag, TYPE_RATIONAL)) != OK) {
261         return res;
262     }
263     return updateImpl(tag, (const void*)data, data_count);
264 }
265
266 status_t CameraMetadata::update(uint32_t tag,
267         const String8 &string) {
268     status_t res;
269     if (mLocked) {
270         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
271         return INVALID_OPERATION;
272     }
273     if ( (res = checkType(tag, TYPE_BYTE)) != OK) {
274         return res;
275     }
276     // string.size() doesn't count the null termination character.
277     return updateImpl(tag, (const void*)string.string(), string.size() + 1);
278 }
279
280 status_t CameraMetadata::updateImpl(uint32_t tag, const void *data,
281         size_t data_count) {
282     status_t res;
283     if (mLocked) {
284         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
285         return INVALID_OPERATION;
286     }
287     int type = get_camera_metadata_tag_type(tag);
288     if (type == -1) {
289         ALOGE("%s: Tag %d not found", __FUNCTION__, tag);
290         return BAD_VALUE;
291     }
292     // Safety check - ensure that data isn't pointing to this metadata, since
293     // that would get invalidated if a resize is needed
294     size_t bufferSize = get_camera_metadata_size(mBuffer);
295     uintptr_t bufAddr = reinterpret_cast<uintptr_t>(mBuffer);
296     uintptr_t dataAddr = reinterpret_cast<uintptr_t>(data);
297     if (dataAddr > bufAddr && dataAddr < (bufAddr + bufferSize)) {
298         ALOGE("%s: Update attempted with data from the same metadata buffer!",
299                 __FUNCTION__);
300         return INVALID_OPERATION;
301     }
302
303     size_t data_size = calculate_camera_metadata_entry_data_size(type,
304             data_count);
305
306     res = resizeIfNeeded(1, data_size);
307
308     if (res == OK) {
309         camera_metadata_entry_t entry;
310         res = find_camera_metadata_entry(mBuffer, tag, &entry);
311         if (res == NAME_NOT_FOUND) {
312             res = add_camera_metadata_entry(mBuffer,
313                     tag, data, data_count);
314         } else if (res == OK) {
315             res = update_camera_metadata_entry(mBuffer,
316                     entry.index, data, data_count, NULL);
317         }
318     }
319
320     if (res != OK) {
321         ALOGE("%s: Unable to update metadata entry %s.%s (%x): %s (%d)",
322                 __FUNCTION__, get_camera_metadata_section_name(tag),
323                 get_camera_metadata_tag_name(tag), tag, strerror(-res), res);
324     }
325
326     IF_ALOGV() {
327         ALOGE_IF(validate_camera_metadata_structure(mBuffer, /*size*/NULL) !=
328                  OK,
329
330                  "%s: Failed to validate metadata structure after update %p",
331                  __FUNCTION__, mBuffer);
332     }
333
334     return res;
335 }
336
337 bool CameraMetadata::exists(uint32_t tag) const {
338     camera_metadata_ro_entry entry;
339     return find_camera_metadata_ro_entry(mBuffer, tag, &entry) == 0;
340 }
341
342 camera_metadata_entry_t CameraMetadata::find(uint32_t tag) {
343     status_t res;
344     camera_metadata_entry entry;
345     if (mLocked) {
346         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
347         entry.count = 0;
348         return entry;
349     }
350     res = find_camera_metadata_entry(mBuffer, tag, &entry);
351     if (CC_UNLIKELY( res != OK )) {
352         entry.count = 0;
353         entry.data.u8 = NULL;
354     }
355     return entry;
356 }
357
358 camera_metadata_ro_entry_t CameraMetadata::find(uint32_t tag) const {
359     status_t res;
360     camera_metadata_ro_entry entry;
361     res = find_camera_metadata_ro_entry(mBuffer, tag, &entry);
362     if (CC_UNLIKELY( res != OK )) {
363         entry.count = 0;
364         entry.data.u8 = NULL;
365     }
366     return entry;
367 }
368
369 status_t CameraMetadata::erase(uint32_t tag) {
370     camera_metadata_entry_t entry;
371     status_t res;
372     if (mLocked) {
373         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
374         return INVALID_OPERATION;
375     }
376     res = find_camera_metadata_entry(mBuffer, tag, &entry);
377     if (res == NAME_NOT_FOUND) {
378         return OK;
379     } else if (res != OK) {
380         ALOGE("%s: Error looking for entry %s.%s (%x): %s %d",
381                 __FUNCTION__,
382                 get_camera_metadata_section_name(tag),
383                 get_camera_metadata_tag_name(tag), tag, strerror(-res), res);
384         return res;
385     }
386     res = delete_camera_metadata_entry(mBuffer, entry.index);
387     if (res != OK) {
388         ALOGE("%s: Error deleting entry %s.%s (%x): %s %d",
389                 __FUNCTION__,
390                 get_camera_metadata_section_name(tag),
391                 get_camera_metadata_tag_name(tag), tag, strerror(-res), res);
392     }
393     return res;
394 }
395
396 void CameraMetadata::dump(int fd, int verbosity, int indentation) const {
397     dump_indented_camera_metadata(mBuffer, fd, verbosity, indentation);
398 }
399
400 status_t CameraMetadata::resizeIfNeeded(size_t extraEntries, size_t extraData) {
401     if (mBuffer == NULL) {
402         mBuffer = allocate_camera_metadata(extraEntries * 2, extraData * 2);
403         if (mBuffer == NULL) {
404             ALOGE("%s: Can't allocate larger metadata buffer", __FUNCTION__);
405             return NO_MEMORY;
406         }
407     } else {
408         size_t currentEntryCount = get_camera_metadata_entry_count(mBuffer);
409         size_t currentEntryCap = get_camera_metadata_entry_capacity(mBuffer);
410         size_t newEntryCount = currentEntryCount +
411                 extraEntries;
412         newEntryCount = (newEntryCount > currentEntryCap) ?
413                 newEntryCount * 2 : currentEntryCap;
414
415         size_t currentDataCount = get_camera_metadata_data_count(mBuffer);
416         size_t currentDataCap = get_camera_metadata_data_capacity(mBuffer);
417         size_t newDataCount = currentDataCount +
418                 extraData;
419         newDataCount = (newDataCount > currentDataCap) ?
420                 newDataCount * 2 : currentDataCap;
421
422         if (newEntryCount > currentEntryCap ||
423                 newDataCount > currentDataCap) {
424             camera_metadata_t *oldBuffer = mBuffer;
425             mBuffer = allocate_camera_metadata(newEntryCount,
426                     newDataCount);
427             if (mBuffer == NULL) {
428                 ALOGE("%s: Can't allocate larger metadata buffer", __FUNCTION__);
429                 return NO_MEMORY;
430             }
431             append_camera_metadata(mBuffer, oldBuffer);
432             free_camera_metadata(oldBuffer);
433         }
434     }
435     return OK;
436 }
437
438 status_t CameraMetadata::readFromParcel(const Parcel& data,
439                                         camera_metadata_t** out) {
440
441     status_t err = OK;
442
443     camera_metadata_t* metadata = NULL;
444
445     if (out) {
446         *out = NULL;
447     }
448
449     // See CameraMetadata::writeToParcel for parcel data layout diagram and explanation.
450     // arg0 = blobSize (int32)
451     int32_t blobSizeTmp = -1;
452     if ((err = data.readInt32(&blobSizeTmp)) != OK) {
453         ALOGE("%s: Failed to read metadata size (error %d %s)",
454               __FUNCTION__, err, strerror(-err));
455         return err;
456     }
457     const size_t blobSize = static_cast<size_t>(blobSizeTmp);
458     const size_t alignment = get_camera_metadata_alignment();
459
460     // Special case: zero blob size means zero sized (NULL) metadata.
461     if (blobSize == 0) {
462         ALOGV("%s: Read 0-sized metadata", __FUNCTION__);
463         return OK;
464     }
465
466     if (blobSize <= alignment) {
467         ALOGE("%s: metadata blob is malformed, blobSize(%zu) should be larger than alignment(%zu)",
468                 __FUNCTION__, blobSize, alignment);
469         return BAD_VALUE;
470     }
471
472     const size_t metadataSize = blobSize - alignment;
473
474     // NOTE: this doesn't make sense to me. shouldn't the blob
475     // know how big it is? why do we have to specify the size
476     // to Parcel::readBlob ?
477     ReadableBlob blob;
478     // arg1 = metadata (blob)
479     do {
480         if ((err = data.readBlob(blobSize, &blob)) != OK) {
481             ALOGE("%s: Failed to read metadata blob (sized %zu). Possible "
482                   " serialization bug. Error %d %s",
483                   __FUNCTION__, blobSize, err, strerror(-err));
484             break;
485         }
486
487         // arg2 = offset (blob)
488         // Must be after blob since we don't know offset until after writeBlob.
489         int32_t offsetTmp;
490         if ((err = data.readInt32(&offsetTmp)) != OK) {
491             ALOGE("%s: Failed to read metadata offsetTmp (error %d %s)",
492                   __FUNCTION__, err, strerror(-err));
493             break;
494         }
495         const size_t offset = static_cast<size_t>(offsetTmp);
496         if (offset >= alignment) {
497             ALOGE("%s: metadata offset(%zu) should be less than alignment(%zu)",
498                     __FUNCTION__, blobSize, alignment);
499             err = BAD_VALUE;
500             break;
501         }
502
503         const uintptr_t metadataStart = reinterpret_cast<uintptr_t>(blob.data()) + offset;
504         const camera_metadata_t* tmp =
505                        reinterpret_cast<const camera_metadata_t*>(metadataStart);
506         ALOGV("%s: alignment is: %zu, metadata start: %p, offset: %zu",
507                 __FUNCTION__, alignment, tmp, offset);
508         metadata = allocate_copy_camera_metadata_checked(tmp, metadataSize);
509         if (metadata == NULL) {
510             // We consider that allocation only fails if the validation
511             // also failed, therefore the readFromParcel was a failure.
512             ALOGE("%s: metadata allocation and copy failed", __FUNCTION__);
513             err = BAD_VALUE;
514         }
515     } while(0);
516     blob.release();
517
518     if (out) {
519         ALOGV("%s: Set out metadata to %p", __FUNCTION__, metadata);
520         *out = metadata;
521     } else if (metadata != NULL) {
522         ALOGV("%s: Freed camera metadata at %p", __FUNCTION__, metadata);
523         free_camera_metadata(metadata);
524     }
525
526     return err;
527 }
528
529 status_t CameraMetadata::writeToParcel(Parcel& data,
530                                        const camera_metadata_t* metadata) {
531     status_t res = OK;
532
533     /**
534      * Below is the camera metadata parcel layout:
535      *
536      * |--------------------------------------------|
537      * |             arg0: blobSize                 |
538      * |              (length = 4)                  |
539      * |--------------------------------------------|<--Skip the rest if blobSize == 0.
540      * |                                            |
541      * |                                            |
542      * |              arg1: blob                    |
543      * | (length = variable, see arg1 layout below) |
544      * |                                            |
545      * |                                            |
546      * |--------------------------------------------|
547      * |              arg2: offset                  |
548      * |              (length = 4)                  |
549      * |--------------------------------------------|
550      */
551
552     // arg0 = blobSize (int32)
553     if (metadata == NULL) {
554         // Write zero blobSize for null metadata.
555         return data.writeInt32(0);
556     }
557
558     /**
559      * Always make the blob size sufficiently larger, as we need put alignment
560      * padding and metadata into the blob. Since we don't know the alignment
561      * offset before writeBlob. Then write the metadata to aligned offset.
562      */
563     const size_t metadataSize = get_camera_metadata_compact_size(metadata);
564     const size_t alignment = get_camera_metadata_alignment();
565     const size_t blobSize = metadataSize + alignment;
566     res = data.writeInt32(static_cast<int32_t>(blobSize));
567     if (res != OK) {
568         return res;
569     }
570
571     size_t offset = 0;
572     /**
573      * arg1 = metadata (blob).
574      *
575      * The blob size is the sum of front padding size, metadata size and back padding
576      * size, which is equal to metadataSize + alignment.
577      *
578      * The blob layout is:
579      * |------------------------------------|<----Start address of the blob (unaligned).
580      * |           front padding            |
581      * |          (size = offset)           |
582      * |------------------------------------|<----Aligned start address of metadata.
583      * |                                    |
584      * |                                    |
585      * |            metadata                |
586      * |       (size = metadataSize)        |
587      * |                                    |
588      * |                                    |
589      * |------------------------------------|
590      * |           back padding             |
591      * |     (size = alignment - offset)    |
592      * |------------------------------------|<----End address of blob.
593      *                                            (Blob start address + blob size).
594      */
595     WritableBlob blob;
596     do {
597         res = data.writeBlob(blobSize, false, &blob);
598         if (res != OK) {
599             break;
600         }
601         const uintptr_t metadataStart = ALIGN_TO(blob.data(), alignment);
602         offset = metadataStart - reinterpret_cast<uintptr_t>(blob.data());
603         ALOGV("%s: alignment is: %zu, metadata start: %p, offset: %zu",
604                 __FUNCTION__, alignment,
605                 reinterpret_cast<const void *>(metadataStart), offset);
606         copy_camera_metadata(reinterpret_cast<void*>(metadataStart), metadataSize, metadata);
607
608         // Not too big of a problem since receiving side does hard validation
609         // Don't check the size since the compact size could be larger
610         if (validate_camera_metadata_structure(metadata, /*size*/NULL) != OK) {
611             ALOGW("%s: Failed to validate metadata %p before writing blob",
612                    __FUNCTION__, metadata);
613         }
614
615     } while(false);
616     blob.release();
617
618     // arg2 = offset (int32)
619     res = data.writeInt32(static_cast<int32_t>(offset));
620
621     return res;
622 }
623
624 status_t CameraMetadata::readFromParcel(Parcel *parcel) {
625
626     ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
627
628     status_t res = OK;
629
630     if (parcel == NULL) {
631         ALOGE("%s: parcel is null", __FUNCTION__);
632         return BAD_VALUE;
633     }
634
635     if (mLocked) {
636         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
637         return INVALID_OPERATION;
638     }
639
640     camera_metadata *buffer = NULL;
641     // TODO: reading should return a status code, in case validation fails
642     res = CameraMetadata::readFromParcel(*parcel, &buffer);
643
644     if (res != NO_ERROR) {
645         ALOGE("%s: Failed to read from parcel. Metadata is unchanged.",
646               __FUNCTION__);
647         return res;
648     }
649
650     clear();
651     mBuffer = buffer;
652
653     return OK;
654 }
655
656 status_t CameraMetadata::writeToParcel(Parcel *parcel) const {
657
658     ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
659
660     if (parcel == NULL) {
661         ALOGE("%s: parcel is null", __FUNCTION__);
662         return BAD_VALUE;
663     }
664
665     return CameraMetadata::writeToParcel(*parcel, mBuffer);
666 }
667
668 void CameraMetadata::swap(CameraMetadata& other) {
669     if (mLocked) {
670         ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
671         return;
672     } else if (other.mLocked) {
673         ALOGE("%s: Other CameraMetadata is locked", __FUNCTION__);
674         return;
675     }
676
677     camera_metadata* thisBuf = mBuffer;
678     camera_metadata* otherBuf = other.mBuffer;
679
680     other.mBuffer = thisBuf;
681     mBuffer = otherBuf;
682 }
683
684 }; // namespace android