gles+egl+drm+gbm实现开机动画

基于安卓开机动画修改

/*

 * Copyright (C) 2007 The Android Open Source Project

 *

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *      http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */

#define LOG_NDEBUG 0

#define LOG_TAG "BootAnimation"

#include <vector>

#include <stdint.h>

#include <inttypes.h>

#include <sys/inotify.h>

#include <sys/poll.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <math.h>

#include <fcntl.h>

#include <signal.h>

#include <time.h>

#if defined(ANDROID)|| defined(__ANDROID__)

#include <utils/misc.h>

#include <cutils/atomic.h>

#include <cutils/properties.h>

#include <androidfw/AssetManager.h>

#include <binder/IPCThreadState.h>

#include <utils/Errors.h>

#include <utils/Log.h>

#include <utils/SystemClock.h>

#include <android-base/properties.h>

#include <ui/DisplayConfig.h>

#include <ui/PixelFormat.h>

#include <ui/Rect.h>

#include <ui/Region.h>

#include <gui/ISurfaceComposer.h>

#include <gui/DisplayEventReceiver.h>

#include <gui/Surface.h>

#include <gui/SurfaceComposerClient.h>

// TODO: Fix Skia.

#pragma GCC diagnostic push

#pragma GCC diagnostic ignored "-Wunused-parameter"

#include <SkBitmap.h>

#include <SkImage.h>

#include <SkStream.h>

#pragma GCC diagnostic pop

#else

extern "C"{

}

#endif

#define GL_GLEXT_PROTOTYPES

#define EGL_EGLEXT_PROTOTYPES

#define   gles2_cy 1

#if gles2_cy

#include <GLES2/gl2.h>

#include <EGL/egl.h>

#include <EGL/eglext.h>

#else

#include <GLES/gl.h>

#include <GLES/glext.h>

#include <EGL/eglext.h>

#endif

#include "RectBCf.h"

#include <errno.h>

#include <string.h>

#include <limits.h>

#include "BootAnimation.h"

#include "SkBitmap.h"

#include <xf86drm.h>

#include <xf86drmMode.h>

#ifndef MIN

#define MIN(x,y) (((x) < (y)) ? (x) : (y))

#endif

#ifndef MAX

#define MAX(x,y) (((x) > (y)) ? (x) : (y))

#endif

#ifndef ARRAY_LENGTH

#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0])

#endif

#define ANIM_PATH_MAX 255

#define STR(x)   #x

#define STRTO(x) STR(x)

namespace android {

static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";

static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip";

static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";

static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";

static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip";

static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";

static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";

static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip";

static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";

static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";

static constexpr const char* PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE = "/product/media/userspace-reboot.zip";

static constexpr const char* OEM_USERSPACE_REBOOT_ANIMATION_FILE = "/oem/media/userspace-reboot.zip";

static constexpr const char* SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE = "/system/media/userspace-reboot.zip";

static const char SYSTEM_DATA_DIR_PATH[] = "/data/system";

static const char SYSTEM_TIME_DIR_NAME[] = "time";

static const char SYSTEM_TIME_DIR_PATH[] = "/data/system/time";

static const char CLOCK_FONT_ASSET[] = "images/clock_font.png";

static const char CLOCK_FONT_ZIP_NAME[] = "clock_font.png";

static const char LAST_TIME_CHANGED_FILE_NAME[] = "last_time_change";

static const char LAST_TIME_CHANGED_FILE_PATH[] = "/data/system/time/last_time_change";

static const char ACCURATE_TIME_FLAG_FILE_NAME[] = "time_is_accurate";

static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_accurate";

static const char TIME_FORMAT_12_HOUR_FLAG_FILE_PATH[] = "/data/system/time/time_format_12_hour";

// Java timestamp format. Don't show the clock if the date is before 2000-01-01 00:00:00.

static const long long ACCURATE_TIME_EPOCH = 946684800000;

static constexpr char FONT_BEGIN_CHAR = ' ';

static constexpr char FONT_END_CHAR = '~' + 1;

static constexpr size_t FONT_NUM_CHARS = FONT_END_CHAR - FONT_BEGIN_CHAR + 1;

static constexpr size_t FONT_NUM_COLS = 16;

static constexpr size_t FONT_NUM_ROWS = FONT_NUM_CHARS / FONT_NUM_COLS;

static const int TEXT_CENTER_VALUE = INT_MAX;

static const int TEXT_MISSING_VALUE = INT_MIN;

static const char EXIT_PROP_NAME[] = "service.bootanim.exit";

static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";

static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;

static constexpr size_t TEXT_POS_LEN_MAX = 16;

// ---------------------------------------------------------------------------

static const int DYNAMIC_COLOR_COUNT = 4;

static const char U_TEXTURE[] = "uTexture";

static const char U_FADE[] = "uFade";

static const char U_CROP_AREA[] = "uCropArea";

static const char U_START_COLOR_PREFIX[] = "uStartColor";

static const char U_END_COLOR_PREFIX[] = "uEndColor";

static const char U_COLOR_PROGRESS[] = "uColorProgress";

static const char A_UV[] = "aUv";

static const char A_POSITION[] = "aPosition";

static const char VERTEX_SHADER_SOURCE[] = R"(

    precision mediump float;

    attribute vec4 aPosition;

    attribute highp vec2 aUv;

    varying highp vec2 vUv;

    void main() {

        gl_Position = aPosition;

        vUv = aUv;

    })";

static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(

    precision mediump float;

    const float cWhiteMaskThreshold = 0.05;

    uniform sampler2D uTexture;

    uniform float uFade;

    uniform float uColorProgress;

    uniform vec3 uStartColor0;

    uniform vec3 uStartColor1;

    uniform vec3 uStartColor2;

    uniform vec3 uStartColor3;

    uniform vec3 uEndColor0;

    uniform vec3 uEndColor1;

    uniform vec3 uEndColor2;

    uniform vec3 uEndColor3;

    varying highp vec2 vUv;

    void main() {

        vec4 mask = texture2D(uTexture, vUv);

        float r = mask.r;

        float g = mask.g;

        float b = mask.b;

        float a = mask.a;

        // If all channels have values, render pixel as a shade of white.

        float useWhiteMask = step(cWhiteMaskThreshold, r)

            * step(cWhiteMaskThreshold, g)

            * step(cWhiteMaskThreshold, b)

            * step(cWhiteMaskThreshold, a);

        vec3 color = r * mix(uStartColor0, uEndColor0, uColorProgress)

                + g * mix(uStartColor1, uEndColor1, uColorProgress)

                + b * mix(uStartColor2, uEndColor2, uColorProgress)

                + a * mix(uStartColor3, uEndColor3, uColorProgress);

        color = mix(color, vec3((r + g + b + a) * 0.25), useWhiteMask);

        gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade));

    })";

static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(

    precision mediump float;

    uniform sampler2D uTexture;

    uniform float uFade;

    varying highp vec2 vUv;

    void main() {

        vec4 color = texture2D(uTexture, vUv);

        gl_FragColor = vec4((1.0 - uFade)*color.x, (1.0 - uFade)*color.y,  (1.0 - uFade)*color.z,  1.0) *color.a;

    })";

static const char TEXT_FRAG_SHADER_SOURCE[] = R"(

    precision mediump float;

    uniform sampler2D uTexture;

    uniform vec4 uCropArea;

    varying highp vec2 vUv;

    void main() {

        vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x),

                       mix(uCropArea.y, uCropArea.w, vUv.y));

        gl_FragColor = texture2D(uTexture, uv);

    })";

static GLfloat quadPositions[] = {

    -0.5f, -0.5f,

    +0.5f, -0.5f,

    +0.5f, +0.5f,

    +0.5f, +0.5f,

    -0.5f, +0.5f,

    -0.5f, -0.5f

};

static GLfloat quadUVs[] = {

    0.0f, 1.0f,

    1.0f, 1.0f,

    1.0f, 0.0f,

    1.0f, 0.0f,

    0.0f, 0.0f,

    0.0f, 1.0f

};


 

BootAnimation::BootAnimation(Callbacks* callbacks)

        : mClockEnabled(false), mTimeIsAccurate(false), mTimeFormat12Hour(false){

   // mSession = new SurfaceComposerClient();

    FileMap::mapC = FileMap::unmapC = 0;

   

    onFirstRef();

}

BootAnimation::~BootAnimation() {

    if (mAnimation != nullptr) {

        releaseAnimation(mAnimation);

        mAnimation = nullptr;

    }

    if(FileMap::mapC != FileMap::unmapC)

        LOGE(TAG,"MEM LEAK %d %d",FileMap::mapC , FileMap::unmapC);

}

void BootAnimation::onFirstRef() {

   // status_t err = mSession->linkToComposerDeath(this);

    //SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));

   

        preloadAnimation();

       

}

void* BootAnimation::session() const {

    return nullptr;

}

void BootAnimation::binderDied() {

    // woah, surfaceflinger died!

    LOGD(TAG,"SurfaceFlinger died, exiting...");

    // calling requestExit() is not enough here because the Surface code

    // might be blocked on a condition variable that will never be updated.

    kill( getpid(), SIGKILL );

    requestExit();

}

#include "etc1.h"

#include <GLES2/gl2ext.h>

struct CompressedTextureInfo {

 bool is_valid; // 是否为一个有效的压缩纹理信息

 GLsizei width;

 GLsizei height;

 GLsizei size;

 GLenum internal_format;

 GLvoid *data;

};

#define ETC1_PKM_HEADER_SIZE 16

status_t BootAnimation::initTexture(FileMap* map, int* width_, int* height_) {

    SkBitmap bitmap(map);

   

    // FileMap memory is never released until application exit.

    // Release it now as the texture is already loaded and the memory used for

    // the packed resource can be released.

   

   

    unsigned char * data =(unsigned char*) map->getDataPtr();

    CompressedTextureInfo textureInfo;

    textureInfo.is_valid = false;

    const etc1_byte *header = data;

    if (!etc1_pkm_is_valid(header)) {

        LOGE(TAG,"LoadTexture: etc1_pkm is not valid");

       // return textureInfo;

    }

    unsigned int w = etc1_pkm_get_width(header);

    unsigned int h = etc1_pkm_get_height(header);

    GLuint size = 8 * ((w + 3) >> 2) * ((h + 3) >> 2);

    GLvoid *texture_data = data + ETC1_PKM_HEADER_SIZE;

    textureInfo.is_valid = true;

    textureInfo.width = w;

    textureInfo.height = h;

    textureInfo.size = size;

    textureInfo.internal_format = GL_ETC1_RGB8_OES;

    void* p  = textureInfo.data = texture_data;

  //  GLint crop[4] = { 0, h, w, -h };

    int tw = 1 << (31 - __builtin_clz(w));

    int th = 1 << (31 - __builtin_clz(h));

    if (tw < w) tw <<= 1;

    if (th < h) th <<= 1;

    switch (bitmap.colorType()) {

        case 6:

            if (!mUseNpotTextures && (tw != w || th != h)) {

               

                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA,

                        GL_UNSIGNED_BYTE, nullptr);

                glTexSubImage2D(GL_TEXTURE_2D, 0,

                        0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p);

                       

            } else {

               

                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,

                        GL_UNSIGNED_BYTE, p);

                       

            }

            break;

        case 2:

            if (!mUseNpotTextures && (tw != w || th != h)) {

                glCompressedTexImage2D(GL_TEXTURE_2D, 0,

                GL_ETC1_RGB8_OES,

                    textureInfo.width, textureInfo.height, 0,textureInfo.size, textureInfo.data);

            } else {

               

                glCompressedTexImage2D(GL_TEXTURE_2D, 0,

                GL_ETC1_RGB8_OES,

                    textureInfo.width, textureInfo.height, 0,textureInfo.size, textureInfo.data);

                   

            }

            break;

        case 0://kRGB_565_SkColorType

            if (!mUseNpotTextures && (tw != w || th != h)) {

                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_ALPHA,

                        GL_UNSIGNED_BYTE, nullptr);

                glTexSubImage2D(GL_TEXTURE_2D, 0,

                        0, 0, w, h, GL_ALPHA, GL_UNSIGNED_BYTE, p);

            } else {

                glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGB,

                        GL_UNSIGNED_BYTE, p);

            }

            break;

        default:

            LOGD(TAG,"this should never happen");

            break;

    }

    delete map;

#if gles2_cy

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

#else

    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);

    #endif

    *width_ = w;

    *height_ = h;

    return NO_ERROR;

}



 

EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) {

    const EGLint attribs[] = {

        EGL_RED_SIZE,   8,

        EGL_GREEN_SIZE, 8,

        EGL_BLUE_SIZE,  8,

        EGL_DEPTH_SIZE, 0,

        EGL_NONE

    };

    EGLint numConfigs;

    EGLConfig config;

    eglChooseConfig(display, attribs, &config, 1, &numConfigs);

    return config;

}

ui_Size BootAnimation::limitSurfaceSize(int width, int height) const {

    ui_Size limited(width, height);

    bool wasLimited = false;

    const float aspectRatio = float(width) / float(height);

    if (mMaxWidth != 0 && width > mMaxWidth) {

        limited.h = mMaxWidth / aspectRatio;

        limited.w= mMaxWidth;

        wasLimited = true;

    }

    if (mMaxHeight != 0 && limited.h > mMaxHeight) {

        limited.h = mMaxHeight;

        limited.w = mMaxHeight * aspectRatio;

        wasLimited = true;

    }

    if(wasLimited)

        LOGD(TAG, "Surface size has been limited to [%dx%d] from [%dx%d]",

             limited.w, limited.h, width, height);

    return limited;

}

extern "C"{


 

static GLuint

create_shader(struct window_b *window, const char *source, GLenum shader_type)

{

   

    GLuint shader;

    #if gles2_cy

    GLint status;

    shader = glCreateShader(shader_type);

    assert(shader != 0);

    glShaderSource(shader, 1, (const char **) &source, NULL);

    glCompileShader(shader);

    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

    if (!status) {

        char log[1000];

        GLsizei len;

        glGetShaderInfoLog(shader, 1000, &len, log);

        fprintf(stderr, "Error: compiling %s: %.*s\n",

            shader_type == GL_VERTEX_SHADER ? "vertex" : "fragment",

            len, log);

        exit(1);

    }

    #endif

    return shader;

}

static const char *vert_shader_text =

    "uniform mat4 rotation;\n"

    "attribute vec4 pos;\n"

    "attribute vec4 color;\n"

    "varying vec4 v_color;\n"

    "void main() {\n"

    "  gl_Position = rotation * pos;\n"

    "  v_color = color;\n"

    "}\n";

static const char *frag_shader_text =

    "precision mediump float;\n"

    "varying vec4 v_color;\n"

    "void main() {\n"

    "  gl_FragColor = v_color;\n"

    "}\n";




 

//cfcfcfcf

static GLuint compileShader(GLenum shaderType, const GLchar *source) {

      GLuint shader = glCreateShader(shaderType);

      glShaderSource(shader, 1, &source, 0);

     glCompileShader(shader);

      GLint isCompiled = 0;

     glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);

      if (isCompiled == GL_FALSE) {

         LOGE(TAG,"Compile shader failed. Shader type: %d", shaderType);

         GLint maxLength = 0;

          glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);

         std::vector<GLchar> errorLog(maxLength);

          glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);

          LOGE(TAG,"Shader compilation error: %s", &errorLog[0]);

          return 0;

     }

      return shader;

  }

  GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {

      GLuint program = glCreateProgram();

     glAttachShader(program, vertexShader);

      glAttachShader(program, fragmentShader);

      glLinkProgram(program);

      GLint isLinked = 0;

      glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);

      if (isLinked == GL_FALSE) {

          LOGE(TAG,"Linking shader failed. Shader handles: vert %d, frag %d",

              vertexShader, fragmentShader);

          return 0;

      }

      return program;

  }

  #include "common.h"

  #include "drm-common.h"

  static struct {

    struct egl egl;

    GLfloat aspect;

    enum mode mode;

    const struct gbm *gbm;

    GLuint program;

    /* uniform handles: */

    GLint modelviewmatrix, modelviewprojectionmatrix, normalmatrix;

    GLint texture, textureuv;

    GLuint vbo;

    GLuint positionsoffset, texcoordsoffset, normalsoffset;

    GLuint tex[2];

} gl;

  static struct egl *egl;

static  struct gbm *gbm;

static struct drm *drm;

}//cccccc

  void BootAnimation::initShaders() {

      bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;

      GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);

      GLuint imageFragmentShader =

          compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled

              ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE

              : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);

      GLuint textFragmentShader =

          compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);

      // Initialize image shader.

      mImageShader = linkShader(vertexShader, imageFragmentShader);

      GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION);

      GLint uvLocation = glGetAttribLocation(mImageShader, A_UV);

      mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE);

      mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE);

      glEnableVertexAttribArray(positionLocation);

      glVertexAttribPointer(positionLocation, 2,  GL_FLOAT, GL_FALSE, 0, quadPositions);

      glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);

      glEnableVertexAttribArray(uvLocation);

      // Initialize text shader.

      mTextShader = linkShader(vertexShader, textFragmentShader);

      positionLocation = glGetAttribLocation(mTextShader, A_POSITION);

      uvLocation = glGetAttribLocation(mTextShader, A_UV);

      mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE);

      mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA);

      glEnableVertexAttribArray(positionLocation);

      glVertexAttribPointer(positionLocation, 2,  GL_FLOAT, GL_FALSE, 0, quadPositions);

      glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);

      glEnableVertexAttribArray(uvLocation);

  }

void BootAnimation::resizeSurface(int newWidth, int newHeight) {

   

}

bool BootAnimation::preloadAnimation() {

    findBootAnimationFile();

    if (!mZipFileName.empty()) {

        mAnimation = loadAnimation(mZipFileName);

        return (mAnimation != nullptr);

    }

    else

        LOGE(TAG,"no anim file found %s",mZipFileName.c_str());

    return false;

}

bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) {

    for (const std::string& f : files) {

      //  LOGD(TAG,"CHK %s",f.c_str());

        if (access(f.c_str(), R_OK) == 0) {

            mZipFileName = f.c_str();

            return true;

        }else{

            LOGE(TAG,"ACCESS err %d %s",errno,strerror(errno));

        }

    }

    return false;

}

void BootAnimation::findBootAnimationFile() {

    static const std::vector<std::string> bootFiles = {

        "/data/bootanimation.zip"

    };

   

    findBootAnimationFileInternal(bootFiles);

   

}

extern volatile int  b_cf_ori;

status_t BootAnimation::readyToRun(void* _display ,void*  _window ) {

     

    int ret = 0;

    const char *device = "/dev/dri/card0";

    const char *video = NULL;

    const char *shadertoy = NULL;

    const char *perfcntr = NULL;

    char mode_str[DRM_DISPLAY_MODE_LEN] = "";

    char *p;

    enum mode mode = SMOOTH;

    uint32_t format = DRM_FORMAT_XRGB8888;

    uint64_t modifier = DRM_FORMAT_MOD_LINEAR;

    int samples = 0;

    int atomic = 0;

    int gears = 0;

    int offscreen = 0;

    int connector_id = -1;

    int opt;

    unsigned int len;

    unsigned int vrefresh = 0;

    unsigned int count = ~0;

    bool surfaceless = false;

    mMaxWidth = 960;

    mMaxHeight = 1080;

    drm = (struct drm *)init_drm_legacy(device, mode_str, connector_id, vrefresh, count);

    if (!drm) {

        printf("failed to initialize %s DRM\n",

               offscreen ? "offscreen" :

               atomic ? "atomic" : "legacy");

        return -1;

    }

    gbm = (struct gbm *)init_gbm(drm->fd, drm->mode->hdisplay, drm->mode->vdisplay,

            format, modifier, surfaceless);

    if (!gbm) {

        printf("failed to initialize GBM\n");

        return -1;

    }

    init_egl(&gl.egl, gbm, samples);

     

    egl = &gl.egl;

   

    #if gles2_bdcy

    initShaders();

    #endif

   

     

    EGLint w, h;

    eglQuerySurface(egl->display, egl->surface, EGL_WIDTH, &w);

    eglQuerySurface(egl->display, egl->surface, EGL_HEIGHT, &h);

     

    mWidth = w;

    mHeight = h;

   

    mTargetInset = -1;

   

    return NO_ERROR;

}


 

static float mapLinear(float x, float a1, float a2, float b1, float b2) {

    return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );

}

void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {

    // Map coordinates from screen space to world space.

    float x0 = mapLinear(xStart, 0, mWidth, -1, 1);

    float y0 = mapLinear(yStart, 0, mHeight, -1, 1);

    float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);

    float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);

    // Update quad vertex positions.

    quadPositions[0] = x0;

    quadPositions[1] = y0;

    quadPositions[2] = x1;

    quadPositions[3] = y0;

    quadPositions[4] = x1;

    quadPositions[5] = y1;

    quadPositions[6] = x1;

    quadPositions[7] = y1;

    quadPositions[8] = x0;

    quadPositions[9] = y1;

    quadPositions[10] = x0;

    quadPositions[11] = y0;

    glDrawArrays(GL_TRIANGLES, 0,

        sizeof(quadPositions) / sizeof(quadPositions[0]) / 2);

}



 

bool BootAnimation::threadLoop() {

    int ret = 0;

rst:

    readyToRun(nullptr,nullptr);

   

    bool result;

    // We have no bootanimation file, so we use the stock android logo

    // animation.

    if (mZipFileName.empty()) {

      //  result = android();

    } else {

        LOGD(TAG,"STRAT MOVIE");

        result = movie();

    }

   

   

    return result;

}

bool BootAnimation::android() {

    return false;

}

void BootAnimation::requestExit(){

    mExitPending = true;

}

void BootAnimation::checkExit() {

    // Allow surface flinger to gracefully request shutdown

   

    requestExit();

     

}

bool BootAnimation::validClock(const Animation::Part& part) {

    return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE;

}

static bool parseTextCoord(const char* str, int* dest) {

    if (strcmp("c", str) == 0) {

        *dest = TEXT_CENTER_VALUE;

        return true;

    }

    char* end;

    int val = (int) strtol(str, &end, 0);

    if (end == str || *end != '\0' || val == INT_MAX || val == INT_MIN) {

        return false;

    }

    *dest = val;

    return true;

}

// Parse two position coordinates. If only string is non-empty, treat it as the y value.

void parsePosition(const char* str1, const char* str2, int* x, int* y) {

    bool success = false;

    if (strlen(str1) == 0) {  // No values were specified

        // success = false

    } else if (strlen(str2) == 0) {  // we have only one value

        if (parseTextCoord(str1, y)) {

            *x = TEXT_CENTER_VALUE;

            success = true;

        }

    } else {

        if (parseTextCoord(str1, x) && parseTextCoord(str2, y)) {

            success = true;

        }

    }

    if (!success) {

        *x = TEXT_MISSING_VALUE;

        *y = TEXT_MISSING_VALUE;

    }

}

// Parse a color represented as an HTML-style 'RRGGBB' string: each pair of

// characters in str is a hex number in [0, 255], which are converted to

// floating point values in the range [0.0, 1.0] and placed in the

// corresponding elements of color.

//

// If the input string isn't valid, parseColor returns false and color is

// left unchanged.

static bool parseColor(const char str[7], float color[3]) {

    float tmpColor[3];

    for (int i = 0; i < 3; i++) {

        int val = 0;

        for (int j = 0; j < 2; j++) {

            val *= 16;

            char c = str[2*i + j];

            if      (c >= '0' && c <= '9') val += c - '0';

            else if (c >= 'A' && c <= 'F') val += (c - 'A') + 10;

            else if (c >= 'a' && c <= 'f') val += (c - 'a') + 10;

            else                           return false;

        }

        tmpColor[i] = static_cast<float>(val) / 255.0f;

    }

    memcpy(color, tmpColor, sizeof(tmpColor));

    return true;

}


 

static bool readFile(ZipFileRO* zip, const char* name, string& outString) {

    ZipEntryRO entry = (ZipEntryRO)zip->findEntryByName(name);

   

    if (!entry) {

        LOGE(TAG, "couldn't find %s", name);

        return false;

    }

    FileMap* entryMap = zip->createEntryFileMap(entry);

    zip->releaseEntry(entry);

   

    if (!entryMap) {

        LOGE(TAG, "entryMap is null");

        return false;

    }

   // string str((char const*)entryMap->getDataPtr(), entryMap->getDataLength());

    outString = string((char const*)entryMap->getDataPtr(), entryMap->getDataLength());

    delete entryMap;

    return true;

}

// The font image should be a 96x2 array of character images.  The

// columns are the printable ASCII characters 0x20 - 0x7f.  The

// top row is regular text; the bottom row is bold.

status_t BootAnimation::initFont(Font* font, const char* fallback) {

    status_t status = NO_ERROR;

   

    return status;

}

void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {

   

}

// We render 12 or 24 hour time.

void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {

   

}

bool BootAnimation::parseAnimationDesc(Animation& animation)  {

    string desString;

    if (!readFile(animation.zip, "desc.txt", desString)) {

        return false;

    }

    char const* s = desString.c_str();

   

    // Parse the description file

    for (;;) {

        const char* endl = strstr(s, "\n");

        if (endl == nullptr) break;

        string line(s, endl - s);

        const char* l = line.c_str();

        LOGD(TAG,"line IS %s",l);

        int fps = 0;

        int width = 0;

        int height = 0;

        int count = 0;

        int pause = 0;

        char path[ANIM_ENTRY_NAME_MAX];

        char color[7] = "000000"; // default to black if unspecified

        char clockPos1[TEXT_POS_LEN_MAX + 1] = "";

        char clockPos2[TEXT_POS_LEN_MAX + 1] = "";

        char pathType;

        if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {

            LOGD(TAG,"> w=%d, h=%d, fps=%d", width, height, fps);

            animation.width = width;

           // height = 512;

            animation.height = height;

            animation.fps = fps;

        } else if (sscanf(l, " %c %d %d %" STRTO(ANIM_PATH_MAX) "s #%6s %16s %16s",

                          &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {

            //SLOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",

            //    pathType, count, pause, path, color, clockPos1, clockPos2);

            Animation::Part part;

            part.playUntilComplete = pathType == 'c';

            part.count = count;

            part.pause = pause;

            part.path = path;

            part.audioData = nullptr;

            part.animation = nullptr;

            if (!parseColor(color, part.backgroundColor)) {

                LOGE(TAG,"> invalid color '#%s'", color);

                part.backgroundColor[0] = 0.0f;

                part.backgroundColor[1] = 0.0f;

                part.backgroundColor[2] = 0.0f;

            }

            parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY);

            animation.parts.push_back(part);

        }

        else if (strcmp(l, "$SYSTEM") == 0) {

            // SLOGD("> SYSTEM");

            Animation::Part part;

            part.playUntilComplete = false;

            part.count = 1;

            part.pause = 0;

            part.audioData = nullptr;

            part.animation = loadAnimation(string(SYSTEM_BOOTANIMATION_FILE));

            if (part.animation != nullptr)

                animation.parts.push_back(part);

        }

        s = ++endl;

    }

    return true;

}

bool BootAnimation::preloadZip(Animation& animation) {

    // read all the data structures

    const size_t pcount = animation.parts.size();

    uint32_t cookie = 0;

    ZipFileRO* zip = animation.zip;

    if (!zip->startIteration((void**)&cookie)) {

        return false;

    }

    ZipEntryRO entry;

    char name[ANIM_ENTRY_NAME_MAX];

    while ((entry = zip->nextEntry((void*)&cookie)) != nullptr) {

        cookie++;//b cf

        const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);

        if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {

            LOGE(TAG,"Error fetching entry file name");

            continue;

        }

        const string entryName(name);

        const char* cp;

       // LOGD(TAG,"enName is %s",entryName.c_str());

        cp = strrchr(entryName.c_str(), OS_PATH_SEPARATOR);

        string path;

        string leaf;

        if (cp == nullptr){

            path = "";

            leaf = entryName;

        }

        else{

            path = string(entryName.c_str(),cp-entryName.c_str());

            leaf = cp+1;

        }

       

     //   LOGD(TAG,"pl is %s %s",leaf.c_str(),path.c_str());

        if (leaf.size() > 0) {

            if (entryName == CLOCK_FONT_ZIP_NAME) {

                FileMap* map = zip->createEntryFileMap(entry);

                if (map) {

                    animation.clockFont.map = map;

                }

                continue;

            }

            for (size_t j = 0; j < pcount; j++) {

           //     LOGD(TAG,"PPATH=%s",animation.parts[j].path.c_str());

                if (path == animation.parts[j].path) {

                    uint16_t method;

                    // supports only stored png files

                    if (zip->getEntryInfo(entry, &method, nullptr, nullptr, nullptr, nullptr, nullptr)) {

                        if (method == 0) {

                            FileMap* map = zip->createEntryFileMap(entry);

                            if (map) {

                                Animation::Part& part(animation.parts[j]);

                                if (leaf == "audio.wav") {

                                    // a part may have at most one audio file

                                    part.audioData = (uint8_t *)map->getDataPtr();

                                    part.audioLength = map->getDataLength();

                                } else if (leaf == "trim.txt") {

                                   // part.trimData.setTo((char const*)map->getDataPtr(),

                                                    //    map->getDataLength());

                                } else {

                                    Animation::Frame frame;

                                    frame.name = leaf;

                                //    LOGD(TAG,"FRAME NAME is %s",leaf.c_str());

                                    frame.map = map;

                                    frame.trimWidth = animation.width;

                                    frame.trimHeight = animation.height;

                                    frame.trimX = 0;

                                    frame.trimY = 0;

                                    part.frames.insert(frame);

                                }

                            }

                        } else {

                            LOGE(TAG,"bootanimation.zip is compressed; must be only stored");

                        }

                    }

                }

            }

        }

    }

   

    return true;

}

bool BootAnimation::movie() {

    if (mAnimation == nullptr) {

        mAnimation = loadAnimation(mZipFileName);

    }

    if (mAnimation == nullptr)

        return false;

   

    bool anyPartHasClock = false;

    for (size_t i=0; i < mAnimation->parts.size(); i++) {

        if(validClock(mAnimation->parts[i])) {

            anyPartHasClock = true;

            break;

        }

    }

    if (!anyPartHasClock) {

        mClockEnabled = false;

    }

    // Check if npot textures are supported

    mUseNpotTextures = false;

    string gl_extensions;

    const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));

    if (!exts) {

        glGetError();

    } else {

        gl_extensions = exts;

        if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != string::npos) ||

            (gl_extensions.find("GL_OES_texture_npot") != string::npos)) {

            mUseNpotTextures = true;

        }

       

        if (gl_extensions.find("GL_OES_compressed_ETC1_RGB8_texture")!= string::npos) {

            LOGD(TAG,"SUPPORT ETC1");

        }

        if (gl_extensions.find("GL_OES_texture_compression_astc") != string::npos) {

            LOGD(TAG,"SUPPORT astc");

        }

    }

    // Blend required to draw time on top of animation frames.

    #if gles2_bcy

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDisable(GL_DITHER);

    glDisable(GL_SCISSOR_TEST);

    glDisable(GL_BLEND);

    glEnable(GL_TEXTURE_2D);

    glBindTexture(GL_TEXTURE_2D, 0);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    #else

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glShadeModel(GL_FLAT);

    glDisable(GL_DITHER);

    glDisable(GL_SCISSOR_TEST);

    glDisable(GL_BLEND);

    glBindTexture(GL_TEXTURE_2D, 0);

    glEnable(GL_TEXTURE_2D);

    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    #endif

    playAnimation(*mAnimation);

   

   

    releaseAnimation(mAnimation);

    mAnimation = nullptr;

    return false;

}

bool BootAnimation::exitPending() const

{

   

    return mExitPending;

}

void BootAnimation::initDynamicColors() {

   

}

static void page_flip_handler(int fd, unsigned int frame,

          unsigned int sec, unsigned int usec, void *data)

{

    /* suppress 'unused parameter' warnings */

    (void)fd, (void)frame, (void)sec, (void)usec;

    int *waiting_for_flip = (int*)data;

    *waiting_for_flip = 0;

}

bool BootAnimation::playAnimation(const Animation& animation) {

    const size_t pcount = animation.parts.size();

    nsecs_t frameDuration = s2ns(1) / animation.fps;

    LOGD(TAG,"%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",

           110);

    int  ret = 0;

    fd_set fds;

    drmEventContext evctx = {

            .version = 2,

            .page_flip_handler = page_flip_handler,

    };

    struct gbm_bo *bo;

    struct drm_fb *fb;

    struct gbm_bo *next_bo;

   

    glViewport(0, 0, mWidth, mHeight);

        glScissor(0, 0, mWidth, mHeight);

    glClearColor(

                    0,

                   0,

                    0,

                    1.0f);

    glClear(GL_COLOR_BUFFER_BIT);

   

    eglSwapBuffers(egl->display, egl->surface);

   

    bo = gbm_surface_lock_front_buffer(gbm->surface);

   

    fb = drm_fb_get_from_bo(bo);

    if (!fb) {

        fprintf(stderr, "Failed to get a new framebuffer BO\n");

        return -1;

    }

    /* set mode: */

    ret = drmModeSetCrtc(drm->fd, drm->crtc_id, fb->fb_id, 0, 0,

            &(drm->connector_id), 1, drm->mode);

    if (ret) {

        printf("failed to set mode: %s\n", strerror(errno));

        return ret;

    }

    for (size_t i=0 ; i<pcount ; i++) {

      //  LOGI(TAG,"PCNT IS %d",i);

        const Animation::Part& part(animation.parts[i]);

        const size_t fcount = part.frames.size();

    //    glViewport(0, 0, mWidth, mHeight);

    //    glScissor(0, 0, mWidth, mHeight);

        glBindTexture(GL_TEXTURE_2D, 0);

        // Handle animation package

        if (part.animation != nullptr) {

            playAnimation(*part.animation);

            if (exitPending())

                break;

            continue; //to next part

        }

//part.backgroundColor[0]

     //    glClearColor(

        //            part.backgroundColor[0],

         //          part.backgroundColor[1],

             //       part.backgroundColor[2],

            //        1.0f);

       

        for (int r=0 ; !part.count || r<part.count ; r++) {

           

            // Exit any non playuntil complete parts immediately

            if(exitPending() && !part.playUntilComplete)

                break;

           // part.backgroundColor[0]

           

           

            for (auto it = part.frames.begin() ; it != part.frames.end() && (!exitPending() || part.playUntilComplete) ; it++) {

               

                //

                if(mExitPending||ret == -1){

                    mExitPending = true;

                    if(ret ==-1)

                        LOGE(TAG,"wl_display_dispatch_pending ERR %d %s",errno,strerror(errno));

                    break;

                }

              //  if(mWidth != window.geometry.width || mHeight != window.geometry.height){

                 //   mWidth = window.geometry.width;

                 //   mHeight = window.geometry.height;

                 //   glViewport(0, 0, mWidth, mHeight);

                 //   glScissor(0, 0, mWidth, mHeight);

               // }

                const int animationX = (mWidth - animation.width) / 2;

                const int animationY = (mHeight - animation.height) / 2;

int waiting_for_flip = 1;

                const Animation::Frame& frame(*it);

                nsecs_t lastFrame = systemTime();

                if (r > 0) {

                    glBindTexture(GL_TEXTURE_2D, frame.tid);

                } else {

                    //if (part.count != 1) {

                        glGenTextures(1, &frame.tid);

                        glBindTexture(GL_TEXTURE_2D, frame.tid);

                       // glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

                      //  glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

                  //  }

                    int w, h;

                 //  LOGD(TAG,"%d %d %d %d %s",mWidth,mHeight,frame.trimHeight,frame.trimWidth,frame.name.c_str());

                    initTexture(frame.map, &w, &h);

                    bool* ttt= (bool*)&(frame.tidDie);

                    *ttt = false;

                }

                const int xc = animationX + frame.trimX;

                const int yc = animationY + frame.trimY;

                #if gles2_cy

             //   glClear(GL_COLOR_BUFFER_BIT);

                static float fade = 0.f;

                if(part.count ==0){

                    static bool shouldAdd = true;

                    if(shouldAdd){

                        fade += 0.01f;

                        if(fade>0.5f)

                            shouldAdd = false;

                    }else

                    {

                        fade -=0.01f;

                        if(fade<=0.f)

                            shouldAdd = true;

                    }

                }

               

                glUseProgram(mImageShader);

                  glUniform1i(mImageTextureLocation, 0);

                 glUniform1f(mImageFadeLocation, fade);

                //  if (animation.dynamicColoringEnabled) {

                    // glUniform1f(mImageColorProgressLocation, colorProgress);

               //   }

                  const int frameDrawY = mHeight - (yc + frame.trimHeight);

                  glEnable(GL_BLEND);

                  drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);

                 

                 glDisable(GL_BLEND);

                #else

             

                #endif

               // LOGI(TAG,"fCNT IS %d",i);

               eglSwapBuffers(egl->display, egl->surface);

                next_bo = gbm_surface_lock_front_buffer(gbm->surface);

                fb = drm_fb_get_from_bo(next_bo);

                if (!fb) {

                    fprintf(stderr, "Failed to get a new framebuffer BO\n");

                    return -1;

                }

               

                /*

                * Here you could also update drm plane layers if you want

                * hw composition

                */

                ret = drmModePageFlip(drm->fd, drm->crtc_id, fb->fb_id,

                        DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip);

                if (ret) {

                    printf("failed to queue page flip: %s\n", strerror(errno));

                    return -1;

                }

                while (waiting_for_flip) {

                    FD_ZERO(&fds);

                    FD_SET(0, &fds);

                    FD_SET(drm->fd, &fds);

                    struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };

                    ret = select(drm->fd + 1, &fds, NULL, NULL, &timeout);

                    if (ret < 0) {

                        LOGE(TAG,"select err: %s\n", strerror(errno));

                        continue;

                       

                    } else if (ret == 0) {

                        LOGE(TAG,"select timeout!\n");

                        mExitPending = true;

                        break;

                       

                    } else if (FD_ISSET(0, &fds)) {

                        printf("user interrupted!\n");

                        mExitPending = true;

                        break;

                    }

                    drmHandleEvent(drm->fd, &evctx);

                }

                gbm_surface_release_buffer(gbm->surface, bo);

                bo = next_bo;

                if(part.count!=0&&frame.tidDie==false){

                    glDeleteTextures(1, &frame.tid);

                    bool* ttt= (bool*)&(frame.tidDie);

                    *ttt = true;

                }

                nsecs_t now = systemTime();

                nsecs_t delay = frameDuration - (now - lastFrame);

                //SLOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));

                lastFrame = now;

                if (delay > 0) {

                    LOGD(TAG,"nanosleep %" PRId64,delay);

                    struct timespec spec;

                    spec.tv_sec  = (now + delay) / 1000000000;

                    spec.tv_nsec = (now + delay) % 1000000000;

                    int err;

                    do {

                        err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, nullptr);

                    } while (err<0 && errno == EINTR);

                }

               // LOGI(TAG,"chk exit %d",i);

               // checkExit();

            }

            usleep(part.pause * ns2us(frameDuration));

            // For infinite parts, we've now played them at least once, so perhaps exit

            if(exitPending() && !part.count && mCurrentInset >= mTargetInset)

                break;

        }

    }

    // Free textures created for looping parts now that the animation is done.

    for (const Animation::Part& part : animation.parts) {

       // if (part.count != 1) {

         

            for (auto it = part.frames.begin(); it != part.frames.end(); it++) {

                const Animation::Frame& frame(*it);

                if(frame.tidDie==false)

                    glDeleteTextures(1, &frame.tid);

            }

        //}

    }

    return true;

}

void BootAnimation::processDisplayEvents() {

    // This will poll mDisplayEventReceiver and if there are new events it'll call

    // displayEventCallback synchronously.

   // mLooper->pollOnce(0);

}

void BootAnimation::handleViewport(nsecs_t timestep) {

   

}

void BootAnimation::releaseAnimation(Animation* animation) const {

    for (vector<Animation::Part>::iterator it = animation->parts.begin();

        it != animation->parts.end(); ++it) {

        if (it->animation)

            releaseAnimation(it->animation);

    }

    if (animation->zip){

        delete animation->zip;

        animation->zip = nullptr;

    }

    delete animation;

}

BootAnimation::Animation* BootAnimation::loadAnimation(const string& fn) {

    if (mLoadedFiles.find(fn) != mLoadedFiles.end()) {

        LOGE(TAG,"File \"%s\" is already loaded. Cyclic ref is not allowed",

            fn.c_str());

        return nullptr;

    }

    ZipFileRO *zip = ZipFileRO::open(fn);

    if (zip == nullptr) {

        LOGE(TAG,"Failed to open animation zip \"%s\": %s",

            fn.c_str(), strerror(errno));

        return nullptr;

    }

    Animation *animation =  new Animation;

    animation->fileName = fn;

    animation->zip = zip;

    animation->clockFont.map = nullptr;

    mLoadedFiles.insert(animation->fileName);

    parseAnimationDesc(*animation);

   

    if (!preloadZip(*animation)) {

        releaseAnimation(animation);

        return nullptr;

    }

   

    mLoadedFiles.erase(fn);

   

    return animation;

}

bool BootAnimation::updateIsTimeAccurate() {

   

    return true;

}



 

// ---------------------------------------------------------------------------

} // namespace android


 

  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无v邪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值