• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 #define LOG_TAG "OMXClient"
19 
20 #ifdef __LP64__
21 #define OMX_ANDROID_COMPILE_AS_32BIT_ON_64BIT_PLATFORMS
22 #endif
23 
24 #include <utils/Log.h>
25 
26 #include <binder/IServiceManager.h>
27 #include <media/IMediaPlayerService.h>
28 #include <media/IMediaCodecService.h>
29 #include <media/stagefright/foundation/ADebug.h>
30 #include <media/stagefright/OMXClient.h>
31 #include <cutils/properties.h>
32 #include <utils/KeyedVector.h>
33 
34 #include "include/OMX.h"
35 
36 namespace android {
37 
38 static bool sCodecProcessEnabled = true;
39 
40 struct MuxOMX : public IOMX {
41     MuxOMX(const sp<IOMX> &mediaServerOMX, const sp<IOMX> &mediaCodecOMX);
42     virtual ~MuxOMX();
43 
44     // Nobody should be calling this. In case someone does anyway, just
45     // return the media server IOMX.
46     // TODO: return NULL
onAsBinderandroid::MuxOMX47     virtual IBinder *onAsBinder() {
48         ALOGE("MuxOMX::onAsBinder should not be called");
49         return IInterface::asBinder(mMediaServerOMX).get();
50     }
51 
52     virtual bool livesLocally(node_id node, pid_t pid);
53 
54     virtual status_t listNodes(List<ComponentInfo> *list);
55 
56     virtual status_t allocateNode(
57             const char *name, const sp<IOMXObserver> &observer,
58             sp<IBinder> *nodeBinder,
59             node_id *node);
60 
61     virtual status_t freeNode(node_id node);
62 
63     virtual status_t sendCommand(
64             node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param);
65 
66     virtual status_t getParameter(
67             node_id node, OMX_INDEXTYPE index,
68             void *params, size_t size);
69 
70     virtual status_t setParameter(
71             node_id node, OMX_INDEXTYPE index,
72             const void *params, size_t size);
73 
74     virtual status_t getConfig(
75             node_id node, OMX_INDEXTYPE index,
76             void *params, size_t size);
77 
78     virtual status_t setConfig(
79             node_id node, OMX_INDEXTYPE index,
80             const void *params, size_t size);
81 
82     virtual status_t getState(
83             node_id node, OMX_STATETYPE* state);
84 
85     virtual status_t storeMetaDataInBuffers(
86             node_id node, OMX_U32 port_index, OMX_BOOL enable, MetadataBufferType *type);
87 
88     virtual status_t prepareForAdaptivePlayback(
89             node_id node, OMX_U32 port_index, OMX_BOOL enable,
90             OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight);
91 
92     virtual status_t configureVideoTunnelMode(
93             node_id node, OMX_U32 portIndex, OMX_BOOL tunneled,
94             OMX_U32 audioHwSync, native_handle_t **sidebandHandle);
95 
96     virtual status_t enableNativeBuffers(
97             node_id node, OMX_U32 port_index, OMX_BOOL graphic, OMX_BOOL enable);
98 
99     virtual status_t getGraphicBufferUsage(
100             node_id node, OMX_U32 port_index, OMX_U32* usage);
101 
102     virtual status_t useBuffer(
103             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
104             buffer_id *buffer, OMX_U32 allottedSize);
105 
106     virtual status_t useGraphicBuffer(
107             node_id node, OMX_U32 port_index,
108             const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer);
109 
110     virtual status_t updateGraphicBufferInMeta(
111             node_id node, OMX_U32 port_index,
112             const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer);
113 
114     virtual status_t updateNativeHandleInMeta(
115             node_id node, OMX_U32 port_index,
116             const sp<NativeHandle> &nativeHandle, buffer_id buffer);
117 
118     virtual status_t createInputSurface(
119             node_id node, OMX_U32 port_index, android_dataspace dataSpace,
120             sp<IGraphicBufferProducer> *bufferProducer, MetadataBufferType *type);
121 
122     virtual status_t createPersistentInputSurface(
123             sp<IGraphicBufferProducer> *bufferProducer,
124             sp<IGraphicBufferConsumer> *bufferConsumer);
125 
126     virtual status_t setInputSurface(
127             node_id node, OMX_U32 port_index,
128             const sp<IGraphicBufferConsumer> &bufferConsumer, MetadataBufferType *type);
129 
130     virtual status_t signalEndOfInputStream(node_id node);
131 
132     virtual status_t allocateSecureBuffer(
133             node_id node, OMX_U32 port_index, size_t size,
134             buffer_id *buffer, void **buffer_data, sp<NativeHandle> *native_handle);
135 
136     virtual status_t allocateBufferWithBackup(
137             node_id node, OMX_U32 port_index, const sp<IMemory> &params,
138             buffer_id *buffer, OMX_U32 allottedSize);
139 
140     virtual status_t freeBuffer(
141             node_id node, OMX_U32 port_index, buffer_id buffer);
142 
143     virtual status_t fillBuffer(node_id node, buffer_id buffer, int fenceFd);
144 
145     virtual status_t emptyBuffer(
146             node_id node,
147             buffer_id buffer,
148             OMX_U32 range_offset, OMX_U32 range_length,
149             OMX_U32 flags, OMX_TICKS timestamp, int fenceFd);
150 
151     virtual status_t getExtensionIndex(
152             node_id node,
153             const char *parameter_name,
154             OMX_INDEXTYPE *index);
155 
156     virtual status_t setInternalOption(
157             node_id node,
158             OMX_U32 port_index,
159             InternalOptionType type,
160             const void *data,
161             size_t size);
162 
163 private:
164     mutable Mutex mLock;
165 
166     sp<IOMX> mMediaServerOMX;
167     sp<IOMX> mMediaCodecOMX;
168     sp<IOMX> mLocalOMX;
169 
170     typedef enum {
171         LOCAL,
172         MEDIAPROCESS,
173         CODECPROCESS
174     } node_location;
175 
176     KeyedVector<node_id, node_location> mNodeLocation;
177 
178     bool isLocalNode(node_id node) const;
179     bool isLocalNode_l(node_id node) const;
180     const sp<IOMX> &getOMX(node_id node) const;
181     const sp<IOMX> &getOMX_l(node_id node) const;
182 
183     static node_location getPreferredCodecLocation(const char *name);
184 
185     DISALLOW_EVIL_CONSTRUCTORS(MuxOMX);
186 };
187 
MuxOMX(const sp<IOMX> & mediaServerOMX,const sp<IOMX> & mediaCodecOMX)188 MuxOMX::MuxOMX(const sp<IOMX> &mediaServerOMX, const sp<IOMX> &mediaCodecOMX)
189     : mMediaServerOMX(mediaServerOMX),
190       mMediaCodecOMX(mediaCodecOMX) {
191     ALOGI("MuxOMX ctor");
192 }
193 
~MuxOMX()194 MuxOMX::~MuxOMX() {
195 }
196 
isLocalNode(node_id node) const197 bool MuxOMX::isLocalNode(node_id node) const {
198     Mutex::Autolock autoLock(mLock);
199 
200     return isLocalNode_l(node);
201 }
202 
isLocalNode_l(node_id node) const203 bool MuxOMX::isLocalNode_l(node_id node) const {
204     return mNodeLocation.valueFor(node) == LOCAL;
205 }
206 
207 // static
getPreferredCodecLocation(const char * name)208 MuxOMX::node_location MuxOMX::getPreferredCodecLocation(const char *name) {
209     if (sCodecProcessEnabled) {
210         // all codecs go to codec process unless excluded using system property, in which case
211         // all non-secure decoders, OMX.google.* codecs and encoders can go in the codec process
212         // (non-OMX.google.* encoders can be excluded using system property.)
213         if ((strcasestr(name, "decoder")
214                         && strcasestr(name, ".secure") != name + strlen(name) - 7)
215                 || (strcasestr(name, "encoder")
216                         && !property_get_bool("media.stagefright.legacyencoder", false))
217                 || !property_get_bool("media.stagefright.less-secure", false)
218                 || !strncasecmp(name, "OMX.google.", 11)) {
219             return CODECPROCESS;
220         }
221         // everything else runs in the media server
222         return MEDIAPROCESS;
223     } else {
224 #ifdef __LP64__
225         // 64 bit processes always run OMX remote on MediaServer
226         return MEDIAPROCESS;
227 #else
228         // 32 bit processes run only OMX.google.* components locally
229         if (!strncasecmp(name, "OMX.google.", 11)) {
230             return LOCAL;
231         }
232         return MEDIAPROCESS;
233 #endif
234     }
235 }
236 
getOMX(node_id node) const237 const sp<IOMX> &MuxOMX::getOMX(node_id node) const {
238     Mutex::Autolock autoLock(mLock);
239     return getOMX_l(node);
240 }
241 
getOMX_l(node_id node) const242 const sp<IOMX> &MuxOMX::getOMX_l(node_id node) const {
243     node_location loc = mNodeLocation.valueFor(node);
244     if (loc == LOCAL) {
245         return mLocalOMX;
246     } else if (loc == MEDIAPROCESS) {
247         return mMediaServerOMX;
248     } else if (loc == CODECPROCESS) {
249         return mMediaCodecOMX;
250     }
251     ALOGE("Couldn't determine node location for node %d: %d, using local", node, loc);
252     return mLocalOMX;
253 }
254 
livesLocally(node_id node,pid_t pid)255 bool MuxOMX::livesLocally(node_id node, pid_t pid) {
256     return getOMX(node)->livesLocally(node, pid);
257 }
258 
listNodes(List<ComponentInfo> * list)259 status_t MuxOMX::listNodes(List<ComponentInfo> *list) {
260     Mutex::Autolock autoLock(mLock);
261 
262     if (mLocalOMX == NULL) {
263         mLocalOMX = new OMX;
264     }
265 
266     return mLocalOMX->listNodes(list);
267 }
268 
allocateNode(const char * name,const sp<IOMXObserver> & observer,sp<IBinder> * nodeBinder,node_id * node)269 status_t MuxOMX::allocateNode(
270         const char *name, const sp<IOMXObserver> &observer,
271         sp<IBinder> *nodeBinder,
272         node_id *node) {
273     Mutex::Autolock autoLock(mLock);
274 
275     sp<IOMX> omx;
276 
277     node_location loc = getPreferredCodecLocation(name);
278     if (loc == CODECPROCESS) {
279         omx = mMediaCodecOMX;
280     } else if (loc == MEDIAPROCESS) {
281         omx = mMediaServerOMX;
282     } else {
283         if (mLocalOMX == NULL) {
284             mLocalOMX = new OMX;
285         }
286         omx = mLocalOMX;
287     }
288 
289     status_t err = omx->allocateNode(name, observer, nodeBinder, node);
290     ALOGV("allocated node_id %x on %s OMX", *node, omx == mMediaCodecOMX ? "codecprocess" :
291             omx == mMediaServerOMX ? "mediaserver" : "local");
292 
293 
294     if (err != OK) {
295         return err;
296     }
297 
298     mNodeLocation.add(*node, loc);
299 
300     return OK;
301 }
302 
freeNode(node_id node)303 status_t MuxOMX::freeNode(node_id node) {
304     Mutex::Autolock autoLock(mLock);
305 
306     status_t err = getOMX_l(node)->freeNode(node);
307 
308     if (err != OK) {
309         return err;
310     }
311 
312     mNodeLocation.removeItem(node);
313 
314     return OK;
315 }
316 
sendCommand(node_id node,OMX_COMMANDTYPE cmd,OMX_S32 param)317 status_t MuxOMX::sendCommand(
318         node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
319     return getOMX(node)->sendCommand(node, cmd, param);
320 }
321 
getParameter(node_id node,OMX_INDEXTYPE index,void * params,size_t size)322 status_t MuxOMX::getParameter(
323         node_id node, OMX_INDEXTYPE index,
324         void *params, size_t size) {
325     return getOMX(node)->getParameter(node, index, params, size);
326 }
327 
setParameter(node_id node,OMX_INDEXTYPE index,const void * params,size_t size)328 status_t MuxOMX::setParameter(
329         node_id node, OMX_INDEXTYPE index,
330         const void *params, size_t size) {
331     return getOMX(node)->setParameter(node, index, params, size);
332 }
333 
getConfig(node_id node,OMX_INDEXTYPE index,void * params,size_t size)334 status_t MuxOMX::getConfig(
335         node_id node, OMX_INDEXTYPE index,
336         void *params, size_t size) {
337     return getOMX(node)->getConfig(node, index, params, size);
338 }
339 
setConfig(node_id node,OMX_INDEXTYPE index,const void * params,size_t size)340 status_t MuxOMX::setConfig(
341         node_id node, OMX_INDEXTYPE index,
342         const void *params, size_t size) {
343     return getOMX(node)->setConfig(node, index, params, size);
344 }
345 
getState(node_id node,OMX_STATETYPE * state)346 status_t MuxOMX::getState(
347         node_id node, OMX_STATETYPE* state) {
348     return getOMX(node)->getState(node, state);
349 }
350 
storeMetaDataInBuffers(node_id node,OMX_U32 port_index,OMX_BOOL enable,MetadataBufferType * type)351 status_t MuxOMX::storeMetaDataInBuffers(
352         node_id node, OMX_U32 port_index, OMX_BOOL enable, MetadataBufferType *type) {
353     return getOMX(node)->storeMetaDataInBuffers(node, port_index, enable, type);
354 }
355 
prepareForAdaptivePlayback(node_id node,OMX_U32 port_index,OMX_BOOL enable,OMX_U32 maxFrameWidth,OMX_U32 maxFrameHeight)356 status_t MuxOMX::prepareForAdaptivePlayback(
357         node_id node, OMX_U32 port_index, OMX_BOOL enable,
358         OMX_U32 maxFrameWidth, OMX_U32 maxFrameHeight) {
359     return getOMX(node)->prepareForAdaptivePlayback(
360             node, port_index, enable, maxFrameWidth, maxFrameHeight);
361 }
362 
configureVideoTunnelMode(node_id node,OMX_U32 portIndex,OMX_BOOL enable,OMX_U32 audioHwSync,native_handle_t ** sidebandHandle)363 status_t MuxOMX::configureVideoTunnelMode(
364         node_id node, OMX_U32 portIndex, OMX_BOOL enable,
365         OMX_U32 audioHwSync, native_handle_t **sidebandHandle) {
366     return getOMX(node)->configureVideoTunnelMode(
367             node, portIndex, enable, audioHwSync, sidebandHandle);
368 }
369 
enableNativeBuffers(node_id node,OMX_U32 port_index,OMX_BOOL graphic,OMX_BOOL enable)370 status_t MuxOMX::enableNativeBuffers(
371         node_id node, OMX_U32 port_index, OMX_BOOL graphic, OMX_BOOL enable) {
372     return getOMX(node)->enableNativeBuffers(node, port_index, graphic, enable);
373 }
374 
getGraphicBufferUsage(node_id node,OMX_U32 port_index,OMX_U32 * usage)375 status_t MuxOMX::getGraphicBufferUsage(
376         node_id node, OMX_U32 port_index, OMX_U32* usage) {
377     return getOMX(node)->getGraphicBufferUsage(node, port_index, usage);
378 }
379 
useBuffer(node_id node,OMX_U32 port_index,const sp<IMemory> & params,buffer_id * buffer,OMX_U32 allottedSize)380 status_t MuxOMX::useBuffer(
381         node_id node, OMX_U32 port_index, const sp<IMemory> &params,
382         buffer_id *buffer, OMX_U32 allottedSize) {
383     return getOMX(node)->useBuffer(node, port_index, params, buffer, allottedSize);
384 }
385 
useGraphicBuffer(node_id node,OMX_U32 port_index,const sp<GraphicBuffer> & graphicBuffer,buffer_id * buffer)386 status_t MuxOMX::useGraphicBuffer(
387         node_id node, OMX_U32 port_index,
388         const sp<GraphicBuffer> &graphicBuffer, buffer_id *buffer) {
389     return getOMX(node)->useGraphicBuffer(
390             node, port_index, graphicBuffer, buffer);
391 }
392 
updateGraphicBufferInMeta(node_id node,OMX_U32 port_index,const sp<GraphicBuffer> & graphicBuffer,buffer_id buffer)393 status_t MuxOMX::updateGraphicBufferInMeta(
394         node_id node, OMX_U32 port_index,
395         const sp<GraphicBuffer> &graphicBuffer, buffer_id buffer) {
396     return getOMX(node)->updateGraphicBufferInMeta(
397             node, port_index, graphicBuffer, buffer);
398 }
399 
updateNativeHandleInMeta(node_id node,OMX_U32 port_index,const sp<NativeHandle> & nativeHandle,buffer_id buffer)400 status_t MuxOMX::updateNativeHandleInMeta(
401         node_id node, OMX_U32 port_index,
402         const sp<NativeHandle> &nativeHandle, buffer_id buffer) {
403     return getOMX(node)->updateNativeHandleInMeta(
404             node, port_index, nativeHandle, buffer);
405 }
406 
createInputSurface(node_id node,OMX_U32 port_index,android_dataspace dataSpace,sp<IGraphicBufferProducer> * bufferProducer,MetadataBufferType * type)407 status_t MuxOMX::createInputSurface(
408         node_id node, OMX_U32 port_index, android_dataspace dataSpace,
409         sp<IGraphicBufferProducer> *bufferProducer, MetadataBufferType *type) {
410     status_t err = getOMX(node)->createInputSurface(
411             node, port_index, dataSpace, bufferProducer, type);
412     return err;
413 }
414 
createPersistentInputSurface(sp<IGraphicBufferProducer> * bufferProducer,sp<IGraphicBufferConsumer> * bufferConsumer)415 status_t MuxOMX::createPersistentInputSurface(
416         sp<IGraphicBufferProducer> *bufferProducer,
417         sp<IGraphicBufferConsumer> *bufferConsumer) {
418     sp<IOMX> omx;
419     {
420         Mutex::Autolock autoLock(mLock);
421         if (property_get_bool("media.stagefright.legacyencoder", false)) {
422             omx = mMediaServerOMX;
423         } else {
424             omx = mMediaCodecOMX;
425         }
426     }
427     return omx->createPersistentInputSurface(
428             bufferProducer, bufferConsumer);
429 }
430 
setInputSurface(node_id node,OMX_U32 port_index,const sp<IGraphicBufferConsumer> & bufferConsumer,MetadataBufferType * type)431 status_t MuxOMX::setInputSurface(
432         node_id node, OMX_U32 port_index,
433         const sp<IGraphicBufferConsumer> &bufferConsumer, MetadataBufferType *type) {
434     return getOMX(node)->setInputSurface(node, port_index, bufferConsumer, type);
435 }
436 
signalEndOfInputStream(node_id node)437 status_t MuxOMX::signalEndOfInputStream(node_id node) {
438     return getOMX(node)->signalEndOfInputStream(node);
439 }
440 
allocateSecureBuffer(node_id node,OMX_U32 port_index,size_t size,buffer_id * buffer,void ** buffer_data,sp<NativeHandle> * native_handle)441 status_t MuxOMX::allocateSecureBuffer(
442         node_id node, OMX_U32 port_index, size_t size,
443         buffer_id *buffer, void **buffer_data, sp<NativeHandle> *native_handle) {
444     return getOMX(node)->allocateSecureBuffer(
445             node, port_index, size, buffer, buffer_data, native_handle);
446 }
447 
allocateBufferWithBackup(node_id node,OMX_U32 port_index,const sp<IMemory> & params,buffer_id * buffer,OMX_U32 allottedSize)448 status_t MuxOMX::allocateBufferWithBackup(
449         node_id node, OMX_U32 port_index, const sp<IMemory> &params,
450         buffer_id *buffer, OMX_U32 allottedSize) {
451     return getOMX(node)->allocateBufferWithBackup(
452             node, port_index, params, buffer, allottedSize);
453 }
454 
freeBuffer(node_id node,OMX_U32 port_index,buffer_id buffer)455 status_t MuxOMX::freeBuffer(
456         node_id node, OMX_U32 port_index, buffer_id buffer) {
457     return getOMX(node)->freeBuffer(node, port_index, buffer);
458 }
459 
fillBuffer(node_id node,buffer_id buffer,int fenceFd)460 status_t MuxOMX::fillBuffer(node_id node, buffer_id buffer, int fenceFd) {
461     return getOMX(node)->fillBuffer(node, buffer, fenceFd);
462 }
463 
emptyBuffer(node_id node,buffer_id buffer,OMX_U32 range_offset,OMX_U32 range_length,OMX_U32 flags,OMX_TICKS timestamp,int fenceFd)464 status_t MuxOMX::emptyBuffer(
465         node_id node,
466         buffer_id buffer,
467         OMX_U32 range_offset, OMX_U32 range_length,
468         OMX_U32 flags, OMX_TICKS timestamp, int fenceFd) {
469     return getOMX(node)->emptyBuffer(
470             node, buffer, range_offset, range_length, flags, timestamp, fenceFd);
471 }
472 
getExtensionIndex(node_id node,const char * parameter_name,OMX_INDEXTYPE * index)473 status_t MuxOMX::getExtensionIndex(
474         node_id node,
475         const char *parameter_name,
476         OMX_INDEXTYPE *index) {
477     return getOMX(node)->getExtensionIndex(node, parameter_name, index);
478 }
479 
setInternalOption(node_id node,OMX_U32 port_index,InternalOptionType type,const void * data,size_t size)480 status_t MuxOMX::setInternalOption(
481         node_id node,
482         OMX_U32 port_index,
483         InternalOptionType type,
484         const void *data,
485         size_t size) {
486     return getOMX(node)->setInternalOption(node, port_index, type, data, size);
487 }
488 
OMXClient()489 OMXClient::OMXClient() {
490     char value[PROPERTY_VALUE_MAX];
491     if (property_get("media.stagefright.codecremote", value, NULL)
492             && (!strcmp("0", value) || !strcasecmp("false", value))) {
493         sCodecProcessEnabled = false;
494     }
495 }
496 
connect()497 status_t OMXClient::connect() {
498     sp<IServiceManager> sm = defaultServiceManager();
499     sp<IBinder> playerbinder = sm->getService(String16("media.player"));
500     sp<IMediaPlayerService> mediaservice = interface_cast<IMediaPlayerService>(playerbinder);
501 
502     if (mediaservice.get() == NULL) {
503         ALOGE("Cannot obtain IMediaPlayerService");
504         return NO_INIT;
505     }
506 
507     sp<IOMX> mediaServerOMX = mediaservice->getOMX();
508     if (mediaServerOMX.get() == NULL) {
509         ALOGE("Cannot obtain mediaserver IOMX");
510         return NO_INIT;
511     }
512 
513     // If we don't want to use the codec process, and the media server OMX
514     // is local, use it directly instead of going through MuxOMX
515     if (!sCodecProcessEnabled &&
516             mediaServerOMX->livesLocally(0 /* node */, getpid())) {
517         mOMX = mediaServerOMX;
518         return OK;
519     }
520 
521     sp<IBinder> codecbinder = sm->getService(String16("media.codec"));
522     sp<IMediaCodecService> codecservice = interface_cast<IMediaCodecService>(codecbinder);
523 
524     if (codecservice.get() == NULL) {
525         ALOGE("Cannot obtain IMediaCodecService");
526         return NO_INIT;
527     }
528 
529     sp<IOMX> mediaCodecOMX = codecservice->getOMX();
530     if (mediaCodecOMX.get() == NULL) {
531         ALOGE("Cannot obtain mediacodec IOMX");
532         return NO_INIT;
533     }
534 
535     mOMX = new MuxOMX(mediaServerOMX, mediaCodecOMX);
536 
537     return OK;
538 }
539 
disconnect()540 void OMXClient::disconnect() {
541     if (mOMX.get() != NULL) {
542         mOMX.clear();
543         mOMX = NULL;
544     }
545 }
546 
547 }  // namespace android
548