ProgramCache.cpp revision 458197de008be8fe561286b09f4edddb2f5c540a
13f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian/*
23f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * Copyright 2013 The Android Open Source Project
33f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian *
43f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * Licensed under the Apache License, Version 2.0 (the "License");
53f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * you may not use this file except in compliance with the License.
63f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * You may obtain a copy of the License at
73f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian *
83f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian *      http://www.apache.org/licenses/LICENSE-2.0
93f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian *
103f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * Unless required by applicable law or agreed to in writing, software
113f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * distributed under the License is distributed on an "AS IS" BASIS,
123f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * See the License for the specific language governing permissions and
143f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * limitations under the License.
153f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian */
163f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
173f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include <GLES2/gl2.h>
183f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include <GLES2/gl2ext.h>
193f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
203f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include <utils/String8.h>
213f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
223f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include "ProgramCache.h"
233f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include "Program.h"
243f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian#include "Description.h"
253f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
263f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopiannamespace android {
273f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian// -----------------------------------------------------------------------------------------------
283f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
293f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
303f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian/*
313f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * A simple formatter class to automatically add the endl and
323f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian * manage the indentation.
333f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian */
343f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
353f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianclass Formatter;
363f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianstatic Formatter& indent(Formatter& f);
373f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianstatic Formatter& dedent(Formatter& f);
383f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
393f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianclass Formatter {
403f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    String8 mString;
413f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    int mIndent;
423f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    typedef Formatter& (*FormaterManipFunc)(Formatter&);
433f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    friend Formatter& indent(Formatter& f);
443f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    friend Formatter& dedent(Formatter& f);
453f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianpublic:
46892f22dcb96927b4a4f9ab7badd6f2c9f4f37c1fAndy McFadden    Formatter() : mIndent(0) {}
47892f22dcb96927b4a4f9ab7badd6f2c9f4f37c1fAndy McFadden
483f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    String8 getString() const {
493f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        return mString;
503f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
513f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
523f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    friend Formatter& operator << (Formatter& out, const char* in) {
533f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        for (int i=0 ; i<out.mIndent ; i++) {
543f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            out.mString.append("    ");
553f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        }
563f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        out.mString.append(in);
573f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        out.mString.append("\n");
583f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        return out;
593f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
603f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    friend inline Formatter& operator << (Formatter& out, const String8& in) {
613f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        return operator << (out, in.string());
623f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
633f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
643f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        return (*func)(to);
653f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
663f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian};
673f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianFormatter& indent(Formatter& f) {
683f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    f.mIndent++;
693f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return f;
703f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
713f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianFormatter& dedent(Formatter& f) {
723f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    f.mIndent--;
733f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return f;
743f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
753f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
763f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian// -----------------------------------------------------------------------------------------------
773f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
783f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianANDROID_SINGLETON_STATIC_INSTANCE(ProgramCache)
793f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
803f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
813f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianProgramCache::ProgramCache() {
823f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
833f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
843f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianProgramCache::~ProgramCache() {
853f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
863f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
873f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianProgramCache::Key ProgramCache::computeKey(const Description& description) {
883f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Key needs;
893f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    needs.set(Key::TEXTURE_MASK,
903f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            (description.mTextureTarget == GL_TEXTURE_EXTERNAL_OES) ? Key::TEXTURE_EXT :
913f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            (description.mTextureTarget == GL_TEXTURE_2D)           ? Key::TEXTURE_2D :
923f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            Key::TEXTURE_OFF)
933f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    .set(Key::PLANE_ALPHA_MASK,
943f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            (description.mPlaneAlpha < 1) ? Key::PLANE_ALPHA_LT_ONE : Key::PLANE_ALPHA_EQ_ONE)
953f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    .set(Key::BLEND_MASK,
963f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            description.mPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
973f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    .set(Key::OPACITY_MASK,
983f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            description.mOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT);
993f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return needs;
1003f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
1013f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1023f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianString8 ProgramCache::generateVertexShader(const Key& needs) {
1033f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Formatter vs;
1043f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.isTexturing()) {
1053f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        vs  << "attribute vec4 texCoords;"
1063f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            << "varying vec2 outTexCoords;";
1073f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1083f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    vs << "attribute vec4 position;"
1093f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian       << "uniform mat4 projection;"
1103f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian       << "uniform mat4 texture;"
1113f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian       << "void main(void) {" << indent
1123f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian       << "gl_Position = projection * position;";
1133f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.isTexturing()) {
1143f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        vs << "outTexCoords = (texture * texCoords).st;";
1153f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1163f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    vs << dedent << "}";
1173f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return vs.getString();
1183f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
1193f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1203f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianString8 ProgramCache::generateFragmentShader(const Key& needs) {
1213f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Formatter fs;
1223f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
1233f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "#extension GL_OES_EGL_image_external : require";
1243f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
125458197de008be8fe561286b09f4edddb2f5c540aMathias Agopian
126458197de008be8fe561286b09f4edddb2f5c540aMathias Agopian    // default precision is required-ish in fragment shaders
127458197de008be8fe561286b09f4edddb2f5c540aMathias Agopian    fs << "precision mediump float;";
128458197de008be8fe561286b09f4edddb2f5c540aMathias Agopian
1293f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
1303f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "uniform samplerExternalOES sampler;"
1313f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian           << "varying vec2 outTexCoords;";
1323f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
1333f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "uniform sampler2D sampler;"
1343f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian           << "varying vec2 outTexCoords;";
1353f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    } else if (needs.getTextureTarget() == Key::TEXTURE_OFF) {
1363f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "uniform vec4 color;";
1373f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1383f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.hasPlaneAlpha()) {
1393f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "uniform float alphaPlane;";
1403f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1413f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    fs << "void main(void) {" << indent;
1423f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.isTexturing()) {
1433f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
1443f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    } else {
1453f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        fs << "gl_FragColor = color;";
1463f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1473f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (needs.hasPlaneAlpha()) {
1483f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        // modulate the alpha value with planeAlpha
1493f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        if (needs.isPremultiplied()) {
1503f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            // ... and the color too if we're premultiplied
1513f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            if (needs.isOpaque()) {
1523f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                // ... we're opaque, only premultiply the color component
1533f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                fs << "gl_FragColor.rgb *= alphaPlane;"
1543f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                   << "gl_FragColor.a = alphaPlane;";
1553f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            } else {
1563f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                fs << "gl_FragColor *= alphaPlane;";
1573f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            }
1583f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        } else {
1593f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            // not premultiplied
1603f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            if (needs.isOpaque()) {
1613f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                fs << "gl_FragColor.a = alphaPlane;";
1623f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            } else {
1633f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian                fs << "gl_FragColor.a *= alphaPlane;";
1643f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            }
1653f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        }
1663f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    } else {
1673f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        if (needs.isOpaque()) {
1683f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian            fs << "gl_FragColor.a = 1.0;";
1693f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        }
1703f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
1713f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    fs << dedent << "}";
1723f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return fs.getString();
1733f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
1743f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1753f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias AgopianProgram* ProgramCache::generateProgram(const Key& needs) {
1763f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    // vertex shader
1773f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    String8 vs = generateVertexShader(needs);
1783f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1793f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    // fragment shader
1803f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    String8 fs = generateFragmentShader(needs);
1813f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1823f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Program* program = new Program(needs, vs.string(), fs.string());
1833f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    return program;
1843f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
1853f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1863f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopianvoid ProgramCache::useProgram(const Description& description) {
1873f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1883f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    // generate the key for the shader based on the description
1893f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Key needs(computeKey(description));
1903f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
1913f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian     // look-up the program in the cache
1923f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    Program* program = mCache.valueFor(needs);
1933f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (program == NULL) {
1943f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        // we didn't find our program, so generate one...
1953f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        nsecs_t time = -systemTime();
1963f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        program = generateProgram(needs);
1973f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        mCache.add(needs, program);
1983f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        time += systemTime();
1993f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
2003f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        //ALOGD(">>> generated new program: needs=%08X, time=%u ms (%d programs)",
2013f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        //        needs.mNeeds, uint32_t(ns2ms(time)), mCache.size());
2023f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
2033f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
2043f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    // here we have a suitable program for this description
2053f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    if (program->isValid()) {
2063f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        program->use();
2073f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian        program->setUniforms(description);
2083f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian    }
2093f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian}
2103f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
2113f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian
2123f84483382be2d528918cc1a6fbc6a7d68e0b181Mathias Agopian} /* namespace android */
213