1e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber/*
2e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * Copyright (C) 2009 The Android Open Source Project
3e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber *
4e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * Licensed under the Apache License, Version 2.0 (the "License");
5e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * you may not use this file except in compliance with the License.
6e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * You may obtain a copy of the License at
7e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber *
8e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber *      http://www.apache.org/licenses/LICENSE-2.0
9e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber *
10e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * Unless required by applicable law or agreed to in writing, software
11e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * distributed under the License is distributed on an "AS IS" BASIS,
12e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * See the License for the specific language governing permissions and
14e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber * limitations under the License.
15e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber */
16e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
17aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber//#define LOG_NDEBUG 0
18aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber#define LOG_TAG "StagefrightMediaScanner"
19aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber#include <utils/Log.h>
20aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
21e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber#include <media/stagefright/StagefrightMediaScanner.h>
22e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
231ce986767ae5998ece6810c2933d0b274c529744Andreas Huber#include <media/mediametadataretriever.h>
241ce986767ae5998ece6810c2933d0b274c529744Andreas Huber#include <private/media/VideoFrame.h>
25e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
26aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber// Sonivox includes
27aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber#include <libsonivox/eas.h>
28aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
29e4a838051de5e56f44c71117073a035b804b5d04Andreas Hubernamespace android {
30e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
318151dc3229888109f4ec699eb6311975b51a05b9Mike LockwoodStagefrightMediaScanner::StagefrightMediaScanner() {}
32e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
33e4a838051de5e56f44c71117073a035b804b5d04Andreas HuberStagefrightMediaScanner::~StagefrightMediaScanner() {}
34e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
35aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huberstatic bool FileHasAcceptableExtension(const char *extension) {
36aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    static const char *kValidExtensions[] = {
37aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        ".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2",
38aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        ".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
39072f5247ef893e683728263a540bb93daafda376Andreas Huber        ".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota",
40eecadb9a84f357fb224592cc77603ff3e7c28f08Andreas Huber        ".mkv", ".mka", ".webm", ".ts", ".fl", ".flac", ".mxmf",
41bc554956128d69d8d2e60365fb6cffe6facf659bAndreas Huber        ".avi", ".mpeg", ".mpg"
42aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    };
43aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    static const size_t kNumValidExtensions =
44aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
45aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
46aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    for (size_t i = 0; i < kNumValidExtensions; ++i) {
47aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        if (!strcasecmp(extension, kValidExtensions[i])) {
48aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            return true;
49aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        }
50aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
51aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
52aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    return false;
53aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber}
54aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
552c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brownstatic MediaScanResult HandleMIDI(
56aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        const char *filename, MediaScannerClient *client) {
57aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    // get the library configuration and do sanity check
58aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    const S_EAS_LIB_CONFIG* pLibConfig = EAS_Config();
59aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if ((pLibConfig == NULL) || (LIB_VERSION != pLibConfig->libVersion)) {
60aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        LOGE("EAS library/header mismatch\n");
612c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_ERROR;
62aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
63aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    EAS_I32 temp;
64aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
65aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    // spin up a new EAS engine
66aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    EAS_DATA_HANDLE easData = NULL;
67aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    EAS_HANDLE easHandle = NULL;
68aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    EAS_RESULT result = EAS_Init(&easData);
69aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (result == EAS_SUCCESS) {
70aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        EAS_FILE file;
71aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        file.path = filename;
72aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        file.fd = 0;
73aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        file.offset = 0;
74aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        file.length = 0;
75aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        result = EAS_OpenFile(easData, &file, &easHandle);
76aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
77aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (result == EAS_SUCCESS) {
78aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        result = EAS_Prepare(easData, easHandle);
79aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
80aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (result == EAS_SUCCESS) {
81aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        result = EAS_ParseMetaData(easData, easHandle, &temp);
82aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
83aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (easHandle) {
84aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        EAS_CloseFile(easData, easHandle);
85aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
86aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (easData) {
87aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        EAS_Shutdown(easData);
88aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
89aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
90aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (result != EAS_SUCCESS) {
912c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_SKIPPED;
92aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
93aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
94aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    char buffer[20];
95aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    sprintf(buffer, "%ld", temp);
962c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    status_t status = client->addStringTag("duration", buffer);
97d4b22ab4889f9b1885bfc0dc45667c846a171a98Marco Nelissen    if (status != OK) {
982c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_ERROR;
992c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    }
1002c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    return MEDIA_SCAN_RESULT_OK;
101aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber}
102aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
1032c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff BrownMediaScanResult StagefrightMediaScanner::processFile(
104e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber        const char *path, const char *mimeType,
105e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber        MediaScannerClient &client) {
1061ce986767ae5998ece6810c2933d0b274c529744Andreas Huber    LOGV("processFile '%s'.", path);
1071ce986767ae5998ece6810c2933d0b274c529744Andreas Huber
108e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber    client.setLocale(locale());
109e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber    client.beginFile();
1102c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    MediaScanResult result = processFileInternal(path, mimeType, client);
1112c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    client.endFile();
1122c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    return result;
1132c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown}
114e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
1152c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff BrownMediaScanResult StagefrightMediaScanner::processFileInternal(
1162c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        const char *path, const char *mimeType,
1172c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        MediaScannerClient &client) {
118aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    const char *extension = strrchr(path, '.');
119aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
120aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (!extension) {
1212c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_SKIPPED;
122aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
123aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
124aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (!FileHasAcceptableExtension(extension)) {
1252c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_SKIPPED;
126aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
127aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
128aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (!strcasecmp(extension, ".mid")
129aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".smf")
130aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".imy")
131aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".midi")
132aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".xmf")
133aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".rtttl")
134aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            || !strcasecmp(extension, ".rtx")
135eecadb9a84f357fb224592cc77603ff3e7c28f08Andreas Huber            || !strcasecmp(extension, ".ota")
136eecadb9a84f357fb224592cc77603ff3e7c28f08Andreas Huber            || !strcasecmp(extension, ".mxmf")) {
1372c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return HandleMIDI(path, &client);
1382c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    }
1392c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown
1402c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    sp<MediaMetadataRetriever> mRetriever(new MediaMetadataRetriever);
1412c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown
1422c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    status_t status = mRetriever->setDataSource(path);
1432c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    if (status) {
1442c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        return MEDIA_SCAN_RESULT_ERROR;
1452c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    }
1462c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown
1472c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    const char *value;
1482c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    if ((value = mRetriever->extractMetadata(
1492c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown                    METADATA_KEY_MIMETYPE)) != NULL) {
1502c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        status = client.setMimeType(value);
1512c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        if (status) {
1522c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown            return MEDIA_SCAN_RESULT_ERROR;
153dc1a26eb7870cfafe4774d0db4613025c427db23Hiroshi Takekawa        }
1542c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    }
1558151dc3229888109f4ec699eb6311975b51a05b9Mike Lockwood
1562c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    struct KeyMap {
1572c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        const char *tag;
1582c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        int key;
1592c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    };
1602c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    static const KeyMap kKeyMap[] = {
1612c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "tracknumber", METADATA_KEY_CD_TRACK_NUMBER },
1622c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "discnumber", METADATA_KEY_DISC_NUMBER },
1632c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "album", METADATA_KEY_ALBUM },
1642c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "artist", METADATA_KEY_ARTIST },
1652c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "albumartist", METADATA_KEY_ALBUMARTIST },
1662c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "composer", METADATA_KEY_COMPOSER },
1672c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "genre", METADATA_KEY_GENRE },
1682c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "title", METADATA_KEY_TITLE },
1692c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "year", METADATA_KEY_YEAR },
1702c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "duration", METADATA_KEY_DURATION },
1712c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "writer", METADATA_KEY_WRITER },
1722c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "compilation", METADATA_KEY_COMPILATION },
1732c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        { "isdrm", METADATA_KEY_IS_DRM },
1742c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    };
1752c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]);
1762c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown
1772c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    for (size_t i = 0; i < kNumEntries; ++i) {
1782c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        const char *value;
1792c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown        if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) {
1802c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown            status = client.addStringTag(kKeyMap[i].tag, value);
181d4b22ab4889f9b1885bfc0dc45667c846a171a98Marco Nelissen            if (status != OK) {
1822c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown                return MEDIA_SCAN_RESULT_ERROR;
183e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber            }
184e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber        }
185e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber    }
186e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
1872c70d4a372a8ce83163f19bbd6ae82483ffbe46bJeff Brown    return MEDIA_SCAN_RESULT_OK;
188e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber}
189e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
190e4a838051de5e56f44c71117073a035b804b5d04Andreas Huberchar *StagefrightMediaScanner::extractAlbumArt(int fd) {
1911ce986767ae5998ece6810c2933d0b274c529744Andreas Huber    LOGV("extractAlbumArt %d", fd);
1921ce986767ae5998ece6810c2933d0b274c529744Andreas Huber
193b1262a8b1dd23abad64465f9ffd25c44facdf4d2James Dong    off64_t size = lseek64(fd, 0, SEEK_END);
194aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    if (size < 0) {
195aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        return NULL;
196aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber    }
197b1262a8b1dd23abad64465f9ffd25c44facdf4d2James Dong    lseek64(fd, 0, SEEK_SET);
198aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
1998151dc3229888109f4ec699eb6311975b51a05b9Mike Lockwood    sp<MediaMetadataRetriever> mRetriever(new MediaMetadataRetriever);
20011eab056dd0133a390169d3581edf3eef26d6a54James Dong    if (mRetriever->setDataSource(fd, 0, size) == OK) {
2011ce986767ae5998ece6810c2933d0b274c529744Andreas Huber        sp<IMemory> mem = mRetriever->extractAlbumArt();
2021ce986767ae5998ece6810c2933d0b274c529744Andreas Huber
2031ce986767ae5998ece6810c2933d0b274c529744Andreas Huber        if (mem != NULL) {
2041ce986767ae5998ece6810c2933d0b274c529744Andreas Huber            MediaAlbumArt *art = static_cast<MediaAlbumArt *>(mem->pointer());
205e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
206aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            char *data = (char *)malloc(art->mSize + 4);
207aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            *(int32_t *)data = art->mSize;
2081ce986767ae5998ece6810c2933d0b274c529744Andreas Huber            memcpy(&data[4], &art[1], art->mSize);
209aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber
210aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber            return data;
211aee3c6394a367abf283936cb8b8bd85ed028c050Andreas Huber        }
212e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber    }
213e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
214e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber    return NULL;
215e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber}
216e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber
217e4a838051de5e56f44c71117073a035b804b5d04Andreas Huber}  // namespace android
218