blob: b6dedaeb5978daad5be88e9ebc8cd75a96c72401 [file] [log] [blame]
Mathias Agopian076b1cc2009-04-10 14:24:30 -07001/*
2**
3** Copyright 2009, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9** http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include <stdlib.h>
19#include <stdio.h>
20
21#include "context.h"
22#include "fp.h"
23#include "state.h"
24#include "matrix.h"
25#include "vertex.h"
26#include "light.h"
27#include "primitives.h"
28#include "texture.h"
29#include "BufferObjectManager.h"
Mathias Agopian076b1cc2009-04-10 14:24:30 -070030#include "TextureObjectManager.h"
Mathias Agopian0a3139a2009-06-10 16:01:54 -070031
Mathias Agopian076b1cc2009-04-10 14:24:30 -070032#include <hardware/gralloc.h>
33#include <hardware/copybit.h>
Mathias Agopian0a3139a2009-06-10 16:01:54 -070034#include <private/ui/android_natives_priv.h>
Mathias Agopian076b1cc2009-04-10 14:24:30 -070035
Mathias Agopian6d2cad22009-06-17 21:18:56 -070036
37#define DEBUG_COPYBIT true
38
Mathias Agopian076b1cc2009-04-10 14:24:30 -070039// ----------------------------------------------------------------------------
40
41namespace android {
42
Mathias Agopian0a3139a2009-06-10 16:01:54 -070043static void textureToCopyBitImage(
44 const GGLSurface* surface, buffer_handle_t buffer, copybit_image_t* img)
45{
Mathias Agopian076b1cc2009-04-10 14:24:30 -070046 img->w = surface->stride;
47 img->h = surface->height;
48 img->format = surface->format;
Mathias Agopian076b1cc2009-04-10 14:24:30 -070049 img->base = surface->data;
Mathias Agopian5911aa92009-06-24 16:55:59 -070050 img->handle = (native_handle_t *)buffer;
Mathias Agopian076b1cc2009-04-10 14:24:30 -070051}
52
53struct clipRectRegion : public copybit_region_t {
Mathias Agopiancf251b92009-06-16 18:08:29 -070054 clipRectRegion(ogles_context_t* c)
55 {
56 scissor_t const* scissor = &c->rasterizer.state.scissor;
57 r.l = scissor->left;
58 r.t = scissor->top;
59 r.r = scissor->right;
60 r.b = scissor->bottom;
61 next = iterate;
Mathias Agopian076b1cc2009-04-10 14:24:30 -070062 }
63private:
64 static int iterate(copybit_region_t const * self, copybit_rect_t* rect) {
Mathias Agopiancf251b92009-06-16 18:08:29 -070065 *rect = static_cast<clipRectRegion const*>(self)->r;
66 const_cast<copybit_region_t *>(self)->next = iterate_done;
67 return 1;
68 }
69 static int iterate_done(copybit_region_t const *, copybit_rect_t*) {
Mathias Agopian076b1cc2009-04-10 14:24:30 -070070 return 0;
71 }
Mathias Agopiancf251b92009-06-16 18:08:29 -070072 copybit_rect_t r;
Mathias Agopian076b1cc2009-04-10 14:24:30 -070073};
74
75static bool supportedCopybitsFormat(int format) {
76 switch (format) {
77 case COPYBIT_FORMAT_RGBA_8888:
78 case COPYBIT_FORMAT_RGB_565:
79 case COPYBIT_FORMAT_BGRA_8888:
80 case COPYBIT_FORMAT_RGBA_5551:
81 case COPYBIT_FORMAT_RGBA_4444:
82 case COPYBIT_FORMAT_YCbCr_422_SP:
83 case COPYBIT_FORMAT_YCbCr_420_SP:
84 return true;
85 default:
86 return false;
87 }
88}
89
90static bool hasAlpha(int format) {
91 switch (format) {
92 case COPYBIT_FORMAT_RGBA_8888:
93 case COPYBIT_FORMAT_BGRA_8888:
94 case COPYBIT_FORMAT_RGBA_5551:
95 case COPYBIT_FORMAT_RGBA_4444:
96 return true;
97 default:
98 return false;
99 }
100}
101
102static inline int fixedToByte(GGLfixed val) {
103 return (val - (val >> 8)) >> 8;
104}
105
106/**
107 * Performs a quick check of the rendering state. If this function returns
108 * false we cannot use the copybit driver.
109 */
110
111static bool checkContext(ogles_context_t* c) {
112
Mathias Agopian0a3139a2009-06-10 16:01:54 -0700113 // By convention copybitQuickCheckContext() has already returned true.
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700114 // avoid checking the same information again.
115
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700116 if (c->copybits.blitEngine == NULL) {
117 LOGD_IF(DEBUG_COPYBIT, "no copybit hal");
118 return false;
119 }
120
121 if (c->rasterizer.state.enables
122 & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG)) {
123 LOGD_IF(DEBUG_COPYBIT, "depth test and/or fog");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700124 return false;
125 }
126
Mathias Agopian0a3139a2009-06-10 16:01:54 -0700127 // Note: The drawSurfaceBuffer is only set for destination
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700128 // surfaces types that are supported by the hardware and
129 // do not have an alpha channel. So we don't have to re-check that here.
130
131 static const int tmu = 0;
132 texture_unit_t& u(c->textures.tmu[tmu]);
133 EGLTextureObject* textureObject = u.texture;
134
135 if (!supportedCopybitsFormat(textureObject->surface.format)) {
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700136 LOGD_IF(DEBUG_COPYBIT, "texture format not supported");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700137 return false;
138 }
139 return true;
140}
141
142
143static bool copybit(GLint x, GLint y,
144 GLint w, GLint h,
145 EGLTextureObject* textureObject,
146 const GLint* crop_rect,
147 int transform,
148 ogles_context_t* c)
149{
150 // We assume checkContext has already been called and has already
151 // returned true.
152
153 const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s;
154
155 y = cbSurface.height - (y + h);
156
157 const GLint Ucr = crop_rect[0];
158 const GLint Vcr = crop_rect[1];
159 const GLint Wcr = crop_rect[2];
160 const GLint Hcr = crop_rect[3];
161
Mathias Agopiane7829b82009-06-23 18:31:06 -0700162 int32_t dsdx = (Wcr << 16); // dsdx = ((Wcr/w)/Wt)*Wt
163 int32_t dtdy = ((-Hcr) << 16); // dtdy = -((Hcr/h)/Ht)*Ht
164 if (transform & COPYBIT_TRANSFORM_ROT_90) {
165 dsdx /= h;
166 dtdy /= w;
167 } else {
168 dsdx /= w;
169 dtdy /= h;
170 }
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700171 if (dsdx < c->copybits.minScale || dsdx > c->copybits.maxScale
172 || dtdy < c->copybits.minScale || dtdy > c->copybits.maxScale) {
173 // The requested scale is out of the range the hardware
174 // can support.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700175 LOGD_IF(DEBUG_COPYBIT,
Mathias Agopiane7829b82009-06-23 18:31:06 -0700176 "scale out of range dsdx=%08x (Wcr=%d / w=%d), "
177 "dtdy=%08x (Hcr=%d / h=%d), Ucr=%d, Vcr=%d",
178 dsdx, Wcr, w, dtdy, Hcr, h, Ucr, Vcr);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700179 return false;
180 }
181
Mathias Agopianaa6e88b2009-06-17 21:58:18 -0700182 // copybit doesn't say anything about filtering, so we can't
183 // discriminate. On msm7k, copybit will always filter.
184 // the code below handles min/mag filters, we keep it as a reference.
185
186#ifdef MIN_MAG_FILTER
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700187 int32_t texelArea = gglMulx(dtdy, dsdx);
188 if (texelArea < FIXED_ONE && textureObject->mag_filter != GL_LINEAR) {
189 // Non-linear filtering on a texture enlargement.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700190 LOGD_IF(DEBUG_COPYBIT, "mag filter is not GL_LINEAR");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700191 return false;
192 }
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700193 if (texelArea > FIXED_ONE && textureObject->min_filter != GL_LINEAR) {
194 // Non-linear filtering on an texture shrink.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700195 LOGD_IF(DEBUG_COPYBIT, "min filter is not GL_LINEAR");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700196 return false;
197 }
Mathias Agopianaa6e88b2009-06-17 21:58:18 -0700198#endif
199
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700200 const uint32_t enables = c->rasterizer.state.enables;
201 int planeAlpha = 255;
202 static const int tmu = 0;
203 texture_t& tev(c->rasterizer.state.texture[tmu]);
204 bool srcTextureHasAlpha = hasAlpha(textureObject->surface.format);
Mathias Agopiane7829b82009-06-23 18:31:06 -0700205 if (!srcTextureHasAlpha) {
206 planeAlpha = fixedToByte(c->currentColorClamped.a);
207 }
208
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700209 switch (tev.env) {
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700210 case GGL_REPLACE:
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700211 break;
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700212 case GGL_MODULATE:
Mathias Agopiane7829b82009-06-23 18:31:06 -0700213 if (! (c->currentColorClamped.r == FIXED_ONE &&
214 c->currentColorClamped.g == FIXED_ONE &&
215 c->currentColorClamped.b == FIXED_ONE)) {
216 LOGD_IF(DEBUG_COPYBIT,
217 "MODULATE and non white color (%08x, %08x, %08x)",
218 c->currentColorClamped.r,
219 c->currentColorClamped.g,
220 c->currentColorClamped.b);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700221 return false;
222 }
Mathias Agopiane7829b82009-06-23 18:31:06 -0700223 if (srcTextureHasAlpha && c->currentColorClamped.a < FIXED_ONE) {
224 LOGD_IF(DEBUG_COPYBIT,
225 "MODULATE and texture w/alpha and alpha=%08x)",
226 c->currentColorClamped.a);
227 return false;
228 }
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700229 break;
230
231 default:
232 // Incompatible texture environment.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700233 LOGD_IF(DEBUG_COPYBIT, "incompatible texture environment");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700234 return false;
235 }
236
237 bool blending = false;
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700238 if ((enables & GGL_ENABLE_BLENDING)
239 && !(c->rasterizer.state.blend.src == GL_ONE
240 && c->rasterizer.state.blend.dst == GL_ZERO)) {
241 // Blending is OK if it is
242 // the exact kind of blending that the copybits hardware supports.
243 // Note: The hardware only supports
244 // GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA,
245 // But the surface flinger uses GL_ONE / GL_ONE_MINUS_SRC_ALPHA.
246 // We substitute GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA in that case,
247 // because the performance is worth it, even if the results are
248 // not correct.
249 if (!((c->rasterizer.state.blend.src == GL_SRC_ALPHA
250 || c->rasterizer.state.blend.src == GL_ONE)
251 && c->rasterizer.state.blend.dst == GL_ONE_MINUS_SRC_ALPHA
252 && c->rasterizer.state.blend.alpha_separate == 0)) {
253 // Incompatible blend mode.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700254 LOGD_IF(DEBUG_COPYBIT, "incompatible blend mode");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700255 return false;
256 }
257 blending = true;
258 } else {
259 // No blending is OK if we are not using alpha.
260 if (srcTextureHasAlpha || planeAlpha != 255) {
261 // Incompatible alpha
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700262 LOGD_IF(DEBUG_COPYBIT, "incompatible alpha");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700263 return false;
264 }
265 }
266
267 if (srcTextureHasAlpha && planeAlpha != 255) {
268 // Can't do two types of alpha at once.
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700269 LOGD_IF(DEBUG_COPYBIT, "src alpha and plane alpha");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700270 return false;
271 }
272
273 // LOGW("calling copybits");
274
275 copybit_device_t* copybit = c->copybits.blitEngine;
Mathias Agopian0a3139a2009-06-10 16:01:54 -0700276
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700277 copybit_image_t dst;
Mathias Agopian0a3139a2009-06-10 16:01:54 -0700278 buffer_handle_t target_hnd = c->copybits.drawSurfaceBuffer;
279 textureToCopyBitImage(&cbSurface, target_hnd, &dst);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700280 copybit_rect_t drect = {x, y, x+w, y+h};
281
282 copybit_image_t src;
Mathias Agopian0a3139a2009-06-10 16:01:54 -0700283 buffer_handle_t source_hnd = textureObject->buffer->handle;
284 textureToCopyBitImage(&textureObject->surface, source_hnd, &src);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700285 copybit_rect_t srect = { Ucr, Vcr + Hcr, Ucr + Wcr, Vcr };
286
287 copybit->set_parameter(copybit, COPYBIT_TRANSFORM, transform);
288 copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, planeAlpha);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700289 copybit->set_parameter(copybit, COPYBIT_DITHER,
290 (enables & GGL_ENABLE_DITHER) ? COPYBIT_ENABLE : COPYBIT_DISABLE);
291
292 clipRectRegion it(c);
293 copybit->stretch(copybit, &dst, &src, &drect, &srect, &it);
294 return true;
295}
296
297/*
298 * Try to draw a triangle fan with copybit, return false if we fail.
299 */
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700300bool drawTriangleFanWithCopybit_impl(ogles_context_t* c, GLint first, GLsizei count)
301{
302 if (!checkContext(c)) {
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700303 return false;
304 }
305
Mathias Agopian7272add2009-06-18 19:31:07 -0700306 // FIXME: we should handle culling here
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700307 c->arrays.compileElements(c, c->vc.vBuffer, 0, 4);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700308
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700309 // we detect if we're dealing with a rectangle, by comparing the
310 // rectangles {v0,v2} and {v1,v3} which should be identical.
311
Mathias Agopianaa6e88b2009-06-17 21:58:18 -0700312 // NOTE: we should check that the rectangle is window aligned, however
313 // if we do that, the optimization won't be taken in a lot of cases.
314 // Since this code is intended to be used with SurfaceFlinger only,
315 // so it's okay...
316
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700317 const vec4_t& v0 = c->vc.vBuffer[0].window;
318 const vec4_t& v1 = c->vc.vBuffer[1].window;
319 const vec4_t& v2 = c->vc.vBuffer[2].window;
320 const vec4_t& v3 = c->vc.vBuffer[3].window;
321 int l = min(v0.x, v2.x);
322 int b = min(v0.y, v2.y);
323 int r = max(v0.x, v2.x);
324 int t = max(v0.y, v2.y);
325 if ((l != min(v1.x, v3.x)) || (b != min(v1.y, v3.y)) ||
326 (r != max(v1.x, v3.x)) || (t != max(v1.y, v3.y))) {
327 LOGD_IF(DEBUG_COPYBIT, "geometry not a rectangle");
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700328 return false;
329 }
Mathias Agopian7272add2009-06-18 19:31:07 -0700330
331 // fetch and transform texture coordinates
332 // NOTE: maybe it would be better to have a "compileElementsAll" method
333 // that would ensure all vertex data are fetched and transformed
334 const transform_t& tr = c->transforms.texture[0].transform;
335 for (size_t i=0 ; i<4 ; i++) {
336 const GLubyte* tp = c->arrays.texture[0].element(i);
337 vertex_t* const v = &c->vc.vBuffer[i];
338 c->arrays.texture[0].fetch(c, v->texture[0].v, tp);
339 // FIXME: we should bail if q!=1
340 c->arrays.tex_transform[0](&tr, &v->texture[0], &v->texture[0]);
341 }
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700342
343 const vec4_t& t0 = c->vc.vBuffer[0].texture[0];
344 const vec4_t& t1 = c->vc.vBuffer[1].texture[0];
345 const vec4_t& t2 = c->vc.vBuffer[2].texture[0];
346 const vec4_t& t3 = c->vc.vBuffer[3].texture[0];
347 int txl = min(t0.x, t2.x);
348 int txb = min(t0.y, t2.y);
349 int txr = max(t0.x, t2.x);
350 int txt = max(t0.y, t2.y);
351 if ((txl != min(t1.x, t3.x)) || (txb != min(t1.y, t3.y)) ||
352 (txr != max(t1.x, t3.x)) || (txt != max(t1.y, t3.y))) {
353 LOGD_IF(DEBUG_COPYBIT, "texcoord not a rectangle");
354 return false;
355 }
356 if ((txl != 0) || (txb != 0) ||
357 (txr != FIXED_ONE) || (txt != FIXED_ONE)) {
358 // we could probably handle this case, if we wanted to
Mathias Agopian7272add2009-06-18 19:31:07 -0700359 LOGD_IF(DEBUG_COPYBIT, "texture is cropped: %08x,%08x,%08x,%08x",
360 txl, txb, txr, txt);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700361 return false;
362 }
363
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700364 // at this point, we know we are dealing with a rectangle, so we
365 // only need to consider 3 vertices for computing the jacobians
366
367 const int dx01 = v1.x - v0.x;
368 const int dy01 = v1.y - v0.y;
369 const int dx02 = v2.x - v0.x;
370 const int dy02 = v2.y - v0.y;
371 const int ds01 = t1.S - t0.S;
372 const int dt01 = t1.T - t0.T;
373 const int ds02 = t2.S - t0.S;
374 const int dt02 = t2.T - t0.T;
375 const int area = dx01*dy02 - dy01*dx02;
376 int dsdx, dsdy, dtdx, dtdy;
377 if (area >= 0) {
378 dsdx = ds01*dy02 - ds02*dy01;
379 dsdy = ds02*dx01 - ds01*dx02;
380 dtdx = dt01*dy02 - dt02*dy01;
381 dtdy = dt02*dx01 - dt01*dx02;
382 } else {
383 dsdx = ds02*dy01 - ds01*dy02;
384 dsdy = ds01*dx02 - ds02*dx01;
385 dtdx = dt02*dy01 - dt01*dy02;
386 dtdy = dt01*dx02 - dt02*dx01;
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700387 }
388
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700389 // here we rely on the fact that we know the transform is
390 // a rigid-body transform AND that it can only rotate in 90 degrees
391 // increments
392
393 int transform = 0;
394 if (dsdx == 0) {
395 // 90 deg rotation case
396 // [ 0 dtdx ]
397 // [ dsdx 0 ]
398 transform |= COPYBIT_TRANSFORM_ROT_90;
399 // FIXME: not sure if FLIP_H and FLIP_V shouldn't be inverted
400 if (dtdx > 0)
401 transform |= COPYBIT_TRANSFORM_FLIP_H;
402 if (dsdy < 0)
403 transform |= COPYBIT_TRANSFORM_FLIP_V;
404 } else {
405 // [ dsdx 0 ]
406 // [ 0 dtdy ]
407 if (dsdx < 0)
408 transform |= COPYBIT_TRANSFORM_FLIP_H;
409 if (dtdy < 0)
410 transform |= COPYBIT_TRANSFORM_FLIP_V;
411 }
412
413 //LOGD("l=%d, b=%d, w=%d, h=%d, tr=%d", x, y, w, h, transform);
414 //LOGD("A=%f\tB=%f\nC=%f\tD=%f",
415 // dsdx/65536.0, dtdx/65536.0, dsdy/65536.0, dtdy/65536.0);
416
417 int x = l >> 4;
418 int y = b >> 4;
419 int w = (r-l) >> 4;
420 int h = (t-b) >> 4;
421 texture_unit_t& u(c->textures.tmu[0]);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700422 EGLTextureObject* textureObject = u.texture;
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700423 GLint tWidth = textureObject->surface.width;
424 GLint tHeight = textureObject->surface.height;
425 GLint crop_rect[4] = {0, tHeight, tWidth, -tHeight};
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700426 const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s;
427 y = cbSurface.height - (y + h);
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700428 return copybit(x, y, w, h, textureObject, crop_rect, transform, c);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700429}
430
431/*
432 * Try to drawTexiOESWithCopybit, return false if we fail.
433 */
434
435bool drawTexiOESWithCopybit_impl(GLint x, GLint y, GLint z,
436 GLint w, GLint h, ogles_context_t* c)
437{
438 // quickly process empty rects
439 if ((w|h) <= 0) {
440 return true;
441 }
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700442 if (!checkContext(c)) {
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700443 return false;
444 }
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700445 texture_unit_t& u(c->textures.tmu[0]);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700446 EGLTextureObject* textureObject = u.texture;
Mathias Agopian6d2cad22009-06-17 21:18:56 -0700447 return copybit(x, y, w, h, textureObject, textureObject->crop_rect, 0, c);
Mathias Agopian076b1cc2009-04-10 14:24:30 -0700448}
449
450} // namespace android
451