From 9748086fe267e21d243a49df1775905094a28dd9 Mon Sep 17 00:00:00 2001 From: Damien Bargiacchi Date: Tue, 29 Mar 2016 14:55:55 -0700 Subject: Check clock accuracy before attempting to display clock The RTC may not be set yet, may have been reset, or may have drifted significantly if the device time hasn't been updated in a long time. Using the the last_time_change file to determine if the time is known to be accurate and only display the clock when that is the case. Bug: 27802041 Change-Id: I845cf9c74fcb4009504f1bab853e04146fa19e1d --- cmds/bootanimation/BootAnimation.cpp | 176 +++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 10 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index c597ed2cc61e..e8fcd3b8db5d 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -18,6 +18,9 @@ #define LOG_TAG "BootAnimation" #include +#include +#include +#include #include #include #include @@ -57,23 +60,29 @@ #include "BootAnimation.h" #include "AudioPlayer.h" -#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip" -#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip" -#define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip" -#define EXIT_PROP_NAME "service.bootanim.exit" - namespace android { +static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; +static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.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 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 EXIT_PROP_NAME[] = "service.bootanim.exit"; static const int ANIM_ENTRY_NAME_MAX = 256; // --------------------------------------------------------------------------- -BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true) { +BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), + mTimeCheckThread(NULL) { mSession = new SurfaceComposerClient(); } -BootAnimation::~BootAnimation() { -} +BootAnimation::~BootAnimation() {} void BootAnimation::onFirstRef() { status_t err = mSession->linkToComposerDeath(this); @@ -638,11 +647,21 @@ bool BootAnimation::preloadZip(Animation& animation) bool BootAnimation::movie() { - Animation* animation = loadAnimation(mZipFileName); if (animation == NULL) return false; + bool anyPartHasClock = false; + for (size_t i=0; i < animation->parts.size(); i++) { + if(animation->parts[i].clockPosY >= 0) { + anyPartHasClock = true; + break; + } + } + if (!anyPartHasClock) { + mClockEnabled = false; + } + // Blend required to draw time on top of animation frames. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glShadeModel(GL_FLAT); @@ -664,7 +683,18 @@ bool BootAnimation::movie() mClockEnabled = clockTextureInitialized; } + if (mClockEnabled && !updateIsTimeAccurate()) { + mTimeCheckThread = new TimeCheckThread(this); + mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL); + } + playAnimation(*animation); + + if (mTimeCheckThread != NULL) { + mTimeCheckThread->requestExit(); + mTimeCheckThread = NULL; + } + releaseAnimation(animation); if (clockTextureInitialized) { @@ -745,7 +775,7 @@ bool BootAnimation::playAnimation(const Animation& animation) // which is equivalent to mHeight - (yc + animation.height) glDrawTexiOES(xc, mHeight - (yc + animation.height), 0, animation.width, animation.height); - if (mClockEnabled && part.clockPosY >= 0) { + if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) { drawTime(mClock, part.clockPosY); } @@ -824,6 +854,132 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) mLoadedFiles.remove(fn); return animation; } + +bool BootAnimation::updateIsTimeAccurate() { + static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days + static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes + + if (mTimeIsAccurate) { + return true; + } + + struct stat statResult; + if(stat(ACCURATE_TIME_FLAG_FILE_PATH, &statResult) == 0) { + mTimeIsAccurate = true; + return true; + } + + FILE* file = fopen(LAST_TIME_CHANGED_FILE_PATH, "r"); + if (file != NULL) { + long long lastChangedTime = 0; + fscanf(file, "%lld", &lastChangedTime); + fclose(file); + if (lastChangedTime > 0) { + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + // Match the Java timestamp format + long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL); + if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST + && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) { + mTimeIsAccurate = true; + } + } + } + + return mTimeIsAccurate; +} + +BootAnimation::TimeCheckThread::TimeCheckThread(BootAnimation* bootAnimation) : Thread(false), + mInotifyFd(-1), mSystemWd(-1), mTimeWd(-1), mBootAnimation(bootAnimation) {} + +BootAnimation::TimeCheckThread::~TimeCheckThread() { + // mInotifyFd may be -1 but that's ok since we're not at risk of attempting to close a valid FD. + close(mInotifyFd); +} + +bool BootAnimation::TimeCheckThread::threadLoop() { + bool shouldLoop = doThreadLoop() && !mBootAnimation->mTimeIsAccurate + && mBootAnimation->mClockEnabled; + if (!shouldLoop) { + close(mInotifyFd); + mInotifyFd = -1; + } + return shouldLoop; +} + +bool BootAnimation::TimeCheckThread::doThreadLoop() { + static constexpr int BUFF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1)); + + // Poll instead of doing a blocking read so the Thread can exit if requested. + struct pollfd pfd = { mInotifyFd, POLLIN, 0 }; + ssize_t pollResult = poll(&pfd, 1, 1000); + + if (pollResult == 0) { + return true; + } else if (pollResult < 0) { + ALOGE("Could not poll inotify events"); + return false; + } + + char buff[BUFF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));; + ssize_t length = read(mInotifyFd, buff, BUFF_LEN); + if (length == 0) { + return true; + } else if (length < 0) { + ALOGE("Could not read inotify events"); + return false; + } + + const struct inotify_event *event; + for (char* ptr = buff; ptr < buff + length; ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *) ptr; + if (event->wd == mSystemWd && strcmp(SYSTEM_TIME_DIR_NAME, event->name) == 0) { + addTimeDirWatch(); + } else if (event->wd == mTimeWd && (strcmp(LAST_TIME_CHANGED_FILE_NAME, event->name) == 0 + || strcmp(ACCURATE_TIME_FLAG_FILE_NAME, event->name) == 0)) { + return !mBootAnimation->updateIsTimeAccurate(); + } + } + + return true; +} + +void BootAnimation::TimeCheckThread::addTimeDirWatch() { + mTimeWd = inotify_add_watch(mInotifyFd, SYSTEM_TIME_DIR_PATH, + IN_CLOSE_WRITE | IN_MOVED_TO | IN_ATTRIB); + if (mTimeWd > 0) { + // No need to watch for the time directory to be created if it already exists + inotify_rm_watch(mInotifyFd, mSystemWd); + mSystemWd = -1; + } +} + +status_t BootAnimation::TimeCheckThread::readyToRun() { + mInotifyFd = inotify_init(); + if (mInotifyFd < 0) { + ALOGE("Could not initialize inotify fd"); + return NO_INIT; + } + + mSystemWd = inotify_add_watch(mInotifyFd, SYSTEM_DATA_DIR_PATH, IN_CREATE | IN_ATTRIB); + if (mSystemWd < 0) { + close(mInotifyFd); + mInotifyFd = -1; + ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); + return NO_INIT; + } + + addTimeDirWatch(); + + if (mBootAnimation->updateIsTimeAccurate()) { + close(mInotifyFd); + mInotifyFd = -1; + return ALREADY_EXISTS; + } + + return NO_ERROR; +} + // --------------------------------------------------------------------------- } -- cgit v1.2.3-59-g8ed1b From dd214a796f8b97070645226d6a61f8651f9e13a0 Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Mon, 27 Jun 2016 17:14:30 -0400 Subject: Support trimmed images in BootAnimation Each frame directory may optionally contain a `trim.txt` to specify how the image was trimmed relative to the animation's full size. See FORMAT.md for more details. Bug: 29055299 Change-Id: I7a291e9a52b4bbe50a29c519f6a4d0e2cc0389d7 --- cmds/bootanimation/BootAnimation.cpp | 71 ++++++++++++++------ cmds/bootanimation/BootAnimation.h | 5 ++ cmds/bootanimation/FORMAT.md | 127 +++++++++++++++++++++++++++++++++++ 3 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 cmds/bootanimation/FORMAT.md (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e8fcd3b8db5d..e849f4b8e9d3 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -596,15 +596,15 @@ bool BootAnimation::preloadZip(Animation& animation) // read all the data structures const size_t pcount = animation.parts.size(); void *cookie = NULL; - ZipFileRO* mZip = animation.zip; - if (!mZip->startIteration(&cookie)) { + ZipFileRO* zip = animation.zip; + if (!zip->startIteration(&cookie)) { return false; } ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; - while ((entry = mZip->nextEntry(cookie)) != NULL) { - const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); + while ((entry = zip->nextEntry(cookie)) != NULL) { + const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX); if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) { ALOGE("Error fetching entry file name"); continue; @@ -614,22 +614,29 @@ bool BootAnimation::preloadZip(Animation& animation) const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { - for (size_t j=0 ; jgetEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { + if (zip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) { if (method == ZipFileRO::kCompressStored) { - FileMap* map = mZip->createEntryFileMap(entry); + FileMap* map = zip->createEntryFileMap(entry); if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { // a part may have at most one audio file part.audioFile = map; + } else if (leaf == "trim.txt") { + part.trimData.setTo((char const*)map->getDataPtr(), + map->getDataLength()); } else { Animation::Frame frame; frame.name = leaf; frame.map = map; + frame.trimWidth = animation.width; + frame.trimHeight = animation.height; + frame.trimX = 0; + frame.trimY = 0; part.frames.add(frame); } } @@ -640,7 +647,33 @@ bool BootAnimation::preloadZip(Animation& animation) } } - mZip->endIteration(cookie); + // If there is trimData present, override the positioning defaults. + for (Animation::Part& part : animation.parts) { + const char* trimDataStr = part.trimData.string(); + for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) { + const char* endl = strstr(trimDataStr, "\n"); + // No more trimData for this part. + if (endl == NULL) { + break; + } + String8 line(trimDataStr, endl - trimDataStr); + const char* lineStr = line.string(); + trimDataStr = ++endl; + int width = 0, height = 0, x = 0, y = 0; + if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) { + Animation::Frame& frame(part.frames.editItemAt(frameIdx)); + frame.trimWidth = width; + frame.trimHeight = height; + frame.trimX = x; + frame.trimY = y; + } else { + ALOGE("Error parsing trim.txt, line: %s", lineStr); + break; + } + } + } + + zip->endIteration(cookie); return true; } @@ -707,12 +740,9 @@ bool BootAnimation::movie() bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); - const int xc = (mWidth - animation.width) / 2; - const int yc = ((mHeight - animation.height) / 2); nsecs_t frameDuration = s2ns(1) / animation.fps; - - Region clearReg(Rect(mWidth, mHeight)); - clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height)); + const int animationX = (mWidth - animation.width) / 2; + const int animationY = (mHeight - animation.height) / 2; for (size_t i=0 ; i= 0) { drawTime(mClock, part.clockPosY); } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 1c3d53a59bb8..a093c9b87e75 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -79,6 +79,10 @@ private: struct Frame { String8 name; FileMap* map; + int trimX; + int trimY; + int trimWidth; + int trimHeight; mutable GLuint tid; bool operator < (const Frame& rhs) const { return name < rhs.name; @@ -90,6 +94,7 @@ private: int clockPosY; // The y position of the clock, in pixels, from the bottom of the // display (the clock is centred horizontally). -1 to disable the clock String8 path; + String8 trimData; SortedVector frames; bool playUntilComplete; float backgroundColor[3]; diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md new file mode 100644 index 000000000000..e4c52f783fa3 --- /dev/null +++ b/cmds/bootanimation/FORMAT.md @@ -0,0 +1,127 @@ +# bootanimation format + +## zipfile paths + +The system selects a boot animation zipfile from the following locations, in order: + + /system/media/bootanimation-encrypted.zip (if getprop("vold.decrypt") = '1') + /system/media/bootanimation.zip + /oem/media/bootanimation.zip + +## zipfile layout + +The `bootanimation.zip` archive file includes: + + desc.txt - a text file + part0 \ + part1 \ directories full of PNG frames + ... / + partN / + +## desc.txt format + +The first line defines the general parameters of the animation: + + WIDTH HEIGHT FPS + + * **WIDTH:** animation width (pixels) + * **HEIGHT:** animation height (pixels) + * **FPS:** frames per second, e.g. 60 + +It is followed by a number of rows of the form: + + TYPE COUNT PAUSE PATH [#RGBHEX CLOCK] + + * **TYPE:** a single char indicating what type of animation segment this is: + + `p` -- this part will play unless interrupted by the end of the boot + + `c` -- this part will play to completion, no matter what + * **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete + * **PAUSE:** number of FRAMES to delay after this part ends + * **PATH:** directory in which to find the frames for this part (e.g. `part0`) + * **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB` + * **CLOCK:** _(OPTIONAL)_ the y-coordinate at which to draw the current time (for watches) + +There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip` +and plays that. + +## loading and playing frames + +Each part is scanned and loaded directly from the zip archive. Within a part directory, every file +(except `trim.txt` and `audio.wav`; see next sections) is expected to be a PNG file that represents +one frame in that part (at the specified resolution). For this reason it is important that frames be +named sequentially (e.g. `part000.png`, `part001.png`, ...) and added to the zip archive in that +order. + +## trim.txt + +To save on memory, textures may be trimmed by their background color. trim.txt sequentially lists +the trim output for each frame in its directory, so the frames may be properly positioned. +Output should be of the form: `WxH+X+Y`. Example: + + 713x165+388+914 + 708x152+388+912 + 707x139+388+911 + 649x92+388+910 + +If the file is not present, each frame is assumed to be the same size as the animation. + +## audio.wav + +Each part may optionally play a `wav` sample when it starts. To enable this for an animation, +you must also include a `audio_conf.txt` file in the ZIP archive. Its format is as follows: + + card= + device= + period_size= + period_count= + +This header is followed by zero or more mixer settings, each with the format: + + mixer "" = + +Here's an example `audio_conf.txt` from Shamu: + + card=0 + device=15 + period_size=1024 + period_count=4 + + mixer "QUAT_MI2S_RX Audio Mixer MultiMedia5" = 1 + mixer "Playback Channel Map" = 0 220 157 195 0 0 0 0 + mixer "QUAT_MI2S_RX Channels" = Two + mixer "BOOST_STUB Right Mixer right" = 1 + mixer "BOOST_STUB Left Mixer left" = 1 + mixer "Compress Playback 9 Volume" = 80 80 + +You will probably need to get these mixer names and values out of `audio_platform_info.xml` +and `mixer_paths.xml` for your device. + +## exiting + +The system will end the boot animation (first completing any incomplete or even entirely unplayed +parts that are of type `c`) when the system is finished booting. (This is accomplished by setting +the system property `service.bootanim.exit` to a nonzero string.) + +## protips + +### PNG compression + +Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.: + + for fn in *.png ; do + zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn} + # or: pngcrush -q .... + done + +Some animations benefit from being reduced to 256 colors: + + pngquant --force --ext .png *.png + # alternatively: mogrify -colors 256 anim-tmp/*/*.png + +### creating the ZIP archive + + cd + zip -0qry -i \*.txt \*.png \*.wav @ ../bootanimation.zip *.txt part* + +Note that the ZIP archive is not actually compressed! The PNG files are already as compressed +as they can reasonably get, and there is unlikely to be any redundancy between files. -- cgit v1.2.3-59-g8ed1b From d6d9a1d0b9cf6fa740d9fe410015b094475c5a4c Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Wed, 8 Jun 2016 00:38:58 -0700 Subject: OpenSL-based audio support for BootAnimation Respects dnd settings for sound playback. Basic implementation uses in-memory wavs as buffers. audioplay::playClip should not be called before previous clip ends. Updated FORMAT.md to reflect no more audio_conf.txt Bug: 29055299 Change-Id: Ifc358d7c85f11b8b54ae6446c40643b87bc567f2 --- cmds/bootanimation/Android.mk | 11 +- cmds/bootanimation/AudioPlayer.cpp | 313 ---------------------------------- cmds/bootanimation/AudioPlayer.h | 48 ------ cmds/bootanimation/BootAnimation.cpp | 46 ++--- cmds/bootanimation/BootAnimation.h | 5 +- cmds/bootanimation/FORMAT.md | 30 +--- cmds/bootanimation/audioplay.cpp | 321 +++++++++++++++++++++++++++++++++++ cmds/bootanimation/audioplay.h | 35 ++++ 8 files changed, 393 insertions(+), 416 deletions(-) delete mode 100644 cmds/bootanimation/AudioPlayer.cpp delete mode 100644 cmds/bootanimation/AudioPlayer.h create mode 100644 cmds/bootanimation/audioplay.cpp create mode 100644 cmds/bootanimation/audioplay.h (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 7c8842ca0231..3cf13d908632 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -3,14 +3,16 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ bootanimation_main.cpp \ - AudioPlayer.cpp \ + audioplay.cpp \ BootAnimation.cpp LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -LOCAL_C_INCLUDES += external/tinyalsa/include +LOCAL_C_INCLUDES += \ + external/tinyalsa/include \ + frameworks/wilhelm/include LOCAL_SHARED_LIBRARIES := \ libcutils \ @@ -23,6 +25,7 @@ LOCAL_SHARED_LIBRARIES := \ libEGL \ libGLESv1_CM \ libgui \ + libOpenSLES \ libtinyalsa LOCAL_MODULE:= bootanimation @@ -33,4 +36,8 @@ ifdef TARGET_32_BIT_SURFACEFLINGER LOCAL_32_BIT_ONLY := true endif +# get asserts to work +APP_OPTIM := debug +LOCAL_CFLAGS += -UNDEBUG + include $(BUILD_EXECUTABLE) diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp deleted file mode 100644 index 293213008d58..000000000000 --- a/cmds/bootanimation/AudioPlayer.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (C) 2014 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 "BootAnim_AudioPlayer" - -#include "AudioPlayer.h" - -#include -#include -#include -#include - -#define ID_RIFF 0x46464952 -#define ID_WAVE 0x45564157 -#define ID_FMT 0x20746d66 -#define ID_DATA 0x61746164 - -// Maximum line length for audio_conf.txt -// We only accept lines less than this length to avoid overflows using sscanf() -#define MAX_LINE_LENGTH 1024 - -struct riff_wave_header { - uint32_t riff_id; - uint32_t riff_sz; - uint32_t wave_id; -}; - -struct chunk_header { - uint32_t id; - uint32_t sz; -}; - -struct chunk_fmt { - uint16_t audio_format; - uint16_t num_channels; - uint32_t sample_rate; - uint32_t byte_rate; - uint16_t block_align; - uint16_t bits_per_sample; -}; - - -namespace android { - -AudioPlayer::AudioPlayer() - : mCard(-1), - mDevice(-1), - mPeriodSize(0), - mPeriodCount(0), - mCurrentFile(NULL) -{ -} - -AudioPlayer::~AudioPlayer() { -} - -static bool setMixerValue(struct mixer* mixer, const char* name, const char* values) -{ - if (!mixer) { - ALOGE("no mixer in setMixerValue"); - return false; - } - struct mixer_ctl *ctl = mixer_get_ctl_by_name(mixer, name); - if (!ctl) { - ALOGE("mixer_get_ctl_by_name failed for %s", name); - return false; - } - - enum mixer_ctl_type type = mixer_ctl_get_type(ctl); - int numValues = mixer_ctl_get_num_values(ctl); - int intValue; - char stringValue[MAX_LINE_LENGTH]; - - for (int i = 0; i < numValues && values; i++) { - // strip leading space - while (*values == ' ') values++; - if (*values == 0) break; - - switch (type) { - case MIXER_CTL_TYPE_BOOL: - case MIXER_CTL_TYPE_INT: - if (sscanf(values, "%d", &intValue) == 1) { - if (mixer_ctl_set_value(ctl, i, intValue) != 0) { - ALOGE("mixer_ctl_set_value failed for %s %d", name, intValue); - } - } else { - ALOGE("Could not parse %s as int for %s", values, name); - } - break; - case MIXER_CTL_TYPE_ENUM: - if (sscanf(values, "%s", stringValue) == 1) { - if (mixer_ctl_set_enum_by_string(ctl, stringValue) != 0) { - ALOGE("mixer_ctl_set_enum_by_string failed for %s %s", name, stringValue); - } - } else { - ALOGE("Could not parse %s as enum for %s", values, name); - } - break; - default: - ALOGE("unsupported mixer type %d for %s", type, name); - break; - } - - values = strchr(values, ' '); - } - - return true; -} - - -/* - * Parse the audio configuration file. - * The file is named audio_conf.txt and must begin with the following header: - * - * card= - * device= - * period_size= - * period_count= - * - * This header is followed by zero or more mixer settings, each with the format: - * mixer "" = - * Since mixer names can contain spaces, the name must be enclosed in double quotes. - * The values in the value list can be integers, booleans (represented by 0 or 1) - * or strings for enum values. - */ -bool AudioPlayer::init(const char* config) -{ - int tempInt; - struct mixer* mixer = NULL; - char name[MAX_LINE_LENGTH]; - - for (;;) { - const char* endl = strstr(config, "\n"); - if (!endl) break; - String8 line(config, endl - config); - if (line.length() >= MAX_LINE_LENGTH) { - ALOGE("Line too long in audio_conf.txt"); - return false; - } - const char* l = line.string(); - - if (sscanf(l, "card=%d", &tempInt) == 1) { - ALOGD("card=%d", tempInt); - mCard = tempInt; - - mixer = mixer_open(mCard); - if (!mixer) { - ALOGE("could not open mixer for card %d", mCard); - return false; - } - } else if (sscanf(l, "device=%d", &tempInt) == 1) { - ALOGD("device=%d", tempInt); - mDevice = tempInt; - } else if (sscanf(l, "period_size=%d", &tempInt) == 1) { - ALOGD("period_size=%d", tempInt); - mPeriodSize = tempInt; - } else if (sscanf(l, "period_count=%d", &tempInt) == 1) { - ALOGD("period_count=%d", tempInt); - mPeriodCount = tempInt; - } else if (sscanf(l, "mixer \"%[0-9a-zA-Z _]s\"", name) == 1) { - const char* values = strchr(l, '='); - if (values) { - values++; // skip '=' - ALOGD("name: \"%s\" = %s", name, values); - setMixerValue(mixer, name, values); - } else { - ALOGE("values missing for name: \"%s\"", name); - } - } - config = ++endl; - } - - mixer_close(mixer); - - if (mCard >= 0 && mDevice >= 0) { - return true; - } - - return false; -} - -void AudioPlayer::playFile(FileMap* fileMap) { - // stop any currently playing sound - requestExitAndWait(); - - mCurrentFile = fileMap; - run("bootanim audio", PRIORITY_URGENT_AUDIO); -} - -bool AudioPlayer::threadLoop() -{ - struct pcm_config config; - struct pcm *pcm = NULL; - bool moreChunks = true; - const struct chunk_fmt* chunkFmt = NULL; - int bufferSize; - const uint8_t* wavData; - size_t wavLength; - const struct riff_wave_header* wavHeader; - - if (mCurrentFile == NULL) { - ALOGE("mCurrentFile is NULL"); - return false; - } - - wavData = (const uint8_t *)mCurrentFile->getDataPtr(); - if (!wavData) { - ALOGE("Could not access WAV file data"); - goto exit; - } - wavLength = mCurrentFile->getDataLength(); - - wavHeader = (const struct riff_wave_header *)wavData; - if (wavLength < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || - (wavHeader->wave_id != ID_WAVE)) { - ALOGE("Error: audio file is not a riff/wave file\n"); - goto exit; - } - wavData += sizeof(*wavHeader); - wavLength -= sizeof(*wavHeader); - - do { - const struct chunk_header* chunkHeader = (const struct chunk_header*)wavData; - if (wavLength < sizeof(*chunkHeader)) { - ALOGE("EOF reading chunk headers"); - goto exit; - } - - wavData += sizeof(*chunkHeader); - wavLength -= sizeof(*chunkHeader); - - switch (chunkHeader->id) { - case ID_FMT: - chunkFmt = (const struct chunk_fmt *)wavData; - wavData += chunkHeader->sz; - wavLength -= chunkHeader->sz; - break; - case ID_DATA: - /* Stop looking for chunks */ - moreChunks = 0; - break; - default: - /* Unknown chunk, skip bytes */ - wavData += chunkHeader->sz; - wavLength -= chunkHeader->sz; - } - } while (moreChunks); - - if (!chunkFmt) { - ALOGE("format not found in WAV file"); - goto exit; - } - - - memset(&config, 0, sizeof(config)); - config.channels = chunkFmt->num_channels; - config.rate = chunkFmt->sample_rate; - config.period_size = mPeriodSize; - config.period_count = mPeriodCount; - config.start_threshold = mPeriodSize / 4; - config.stop_threshold = INT_MAX; - config.avail_min = config.start_threshold; - if (chunkFmt->bits_per_sample != 16) { - ALOGE("only 16 bit WAV files are supported"); - goto exit; - } - config.format = PCM_FORMAT_S16_LE; - - pcm = pcm_open(mCard, mDevice, PCM_OUT, &config); - if (!pcm || !pcm_is_ready(pcm)) { - ALOGE("Unable to open PCM device (%s)\n", pcm_get_error(pcm)); - goto exit; - } - - bufferSize = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm)); - - while (wavLength > 0) { - if (exitPending()) goto exit; - size_t count = bufferSize; - if (count > wavLength) - count = wavLength; - - if (pcm_write(pcm, wavData, count)) { - ALOGE("pcm_write failed (%s)", pcm_get_error(pcm)); - goto exit; - } - wavData += count; - wavLength -= count; - } - -exit: - if (pcm) - pcm_close(pcm); - delete mCurrentFile; - mCurrentFile = NULL; - return false; -} - -} // namespace android diff --git a/cmds/bootanimation/AudioPlayer.h b/cmds/bootanimation/AudioPlayer.h deleted file mode 100644 index 1def0aeac8d1..000000000000 --- a/cmds/bootanimation/AudioPlayer.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2014 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. - */ - -#ifndef _BOOTANIMATION_AUDIOPLAYER_H -#define _BOOTANIMATION_AUDIOPLAYER_H - -#include -#include - -namespace android { - -class AudioPlayer : public Thread -{ -public: - AudioPlayer(); - virtual ~AudioPlayer(); - bool init(const char* config); - - void playFile(FileMap* fileMap); - -private: - virtual bool threadLoop(); - -private: - int mCard; // ALSA card to use - int mDevice; // ALSA device to use - int mPeriodSize; - int mPeriodCount; - - FileMap* mCurrentFile; -}; - -} // namespace android - -#endif // _BOOTANIMATION_AUDIOPLAYER_H diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e849f4b8e9d3..6789e2f7ea2b 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -58,7 +58,7 @@ #include #include "BootAnimation.h" -#include "AudioPlayer.h" +#include "audioplay.h" namespace android { @@ -106,9 +106,7 @@ void BootAnimation::binderDied(const wp&) // might be blocked on a condition variable that will never be updated. kill( getpid(), SIGKILL ); requestExit(); - if (mAudioPlayer != NULL) { - mAudioPlayer->requestExit(); - } + audioplay::destroy(); } status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, @@ -400,9 +398,6 @@ void BootAnimation::checkExit() { int exitnow = atoi(value); if (exitnow) { requestExit(); - if (mAudioPlayer != NULL) { - mAudioPlayer->requestExit(); - } } } @@ -524,16 +519,6 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) } char const* s = desString.string(); - // Create and initialize an AudioPlayer if we have an audio_conf.txt file - String8 audioConf; - if (readFile(animation.zip, "audio_conf.txt", audioConf)) { - mAudioPlayer = new AudioPlayer; - if (!mAudioPlayer->init(audioConf.string())) { - ALOGE("mAudioPlayer.init failed"); - mAudioPlayer = NULL; - } - } - // Parse the description file for (;;) { const char* endl = strstr(s, "\n"); @@ -564,7 +549,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.pause = pause; part.path = path; part.clockPosY = clockPosY; - part.audioFile = NULL; + part.audioData = NULL; part.animation = NULL; if (!parseColor(color, part.backgroundColor)) { ALOGE("> invalid color '#%s'", color); @@ -580,7 +565,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.playUntilComplete = false; part.count = 1; part.pause = 0; - part.audioFile = NULL; + part.audioData = NULL; part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE)); if (part.animation != NULL) animation.parts.add(part); @@ -601,6 +586,7 @@ bool BootAnimation::preloadZip(Animation& animation) return false; } + bool hasAudio = false; ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; while ((entry = zip->nextEntry(cookie)) != NULL) { @@ -624,8 +610,10 @@ bool BootAnimation::preloadZip(Animation& animation) if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { + hasAudio = true; // a part may have at most one audio file - part.audioFile = map; + part.audioData = (uint8_t *)map->getDataPtr(); + part.audioLength = map->getDataLength(); } else if (leaf == "trim.txt") { part.trimData.setTo((char const*)map->getDataPtr(), map->getDataLength()); @@ -640,6 +628,8 @@ bool BootAnimation::preloadZip(Animation& animation) part.frames.add(frame); } } + } else { + ALOGE("bootanimation.zip is compressed; must be only stored"); } } } @@ -673,6 +663,12 @@ bool BootAnimation::preloadZip(Animation& animation) } } + // Create and initialize audioplay if there is a wav file in any of the animations. + if (hasAudio) { + ALOGD("found audio.wav, creating playback engine"); + audioplay::create(); + } + zip->endIteration(cookie); return true; @@ -763,8 +759,9 @@ bool BootAnimation::playAnimation(const Animation& animation) break; // only play audio file the first time we animate the part - if (r == 0 && mAudioPlayer != NULL && part.audioFile) { - mAudioPlayer->playFile(part.audioFile); + if (r == 0 && part.audioData) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); } glClearColor( @@ -847,6 +844,11 @@ bool BootAnimation::playAnimation(const Animation& animation) } } } + + // we've finally played everything we're going to play + audioplay::setPlaying(false); + audioplay::destroy(); + return true; } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index a093c9b87e75..5feea0187926 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -30,7 +30,6 @@ class SkBitmap; namespace android { -class AudioPlayer; class Surface; class SurfaceComposerClient; class SurfaceControl; @@ -98,7 +97,8 @@ private: SortedVector frames; bool playUntilComplete; float backgroundColor[3]; - FileMap* audioFile; + uint8_t* audioData; + int audioLength; Animation* animation; }; int fps; @@ -124,7 +124,6 @@ private: void checkExit(); sp mSession; - sp mAudioPlayer; AssetManager mAssets; Texture mAndroid[2]; Texture mClock; diff --git a/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index e4c52f783fa3..9ea6fea966f2 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -67,34 +67,8 @@ If the file is not present, each frame is assumed to be the same size as the ani ## audio.wav -Each part may optionally play a `wav` sample when it starts. To enable this for an animation, -you must also include a `audio_conf.txt` file in the ZIP archive. Its format is as follows: - - card= - device= - period_size= - period_count= - -This header is followed by zero or more mixer settings, each with the format: - - mixer "" = - -Here's an example `audio_conf.txt` from Shamu: - - card=0 - device=15 - period_size=1024 - period_count=4 - - mixer "QUAT_MI2S_RX Audio Mixer MultiMedia5" = 1 - mixer "Playback Channel Map" = 0 220 157 195 0 0 0 0 - mixer "QUAT_MI2S_RX Channels" = Two - mixer "BOOST_STUB Right Mixer right" = 1 - mixer "BOOST_STUB Left Mixer left" = 1 - mixer "Compress Playback 9 Volume" = 80 80 - -You will probably need to get these mixer names and values out of `audio_platform_info.xml` -and `mixer_paths.xml` for your device. +Each part may optionally play a `wav` sample when it starts. To enable this, add a file +with the name `audio.wav` in the part directory. ## exiting diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp new file mode 100644 index 000000000000..e20ef0c7c6cc --- /dev/null +++ b/cmds/bootanimation/audioplay.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2016 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. + * + */ + +// cribbed from samples/native-audio + +#include "audioplay.h" + +#define CHATTY ALOGD + +#include +#include + +#include + +// for native audio +#include +#include + +namespace audioplay { +namespace { + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +static SLPlayItf bqPlayerPlay; +static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue; +static SLMuteSoloItf bqPlayerMuteSolo; +static SLVolumeItf bqPlayerVolume; + +// pointer and size of the next player buffer to enqueue, and number of remaining buffers +static const uint8_t* nextBuffer; +static unsigned nextSize; + +static const uint32_t ID_RIFF = 0x46464952; +static const uint32_t ID_WAVE = 0x45564157; +static const uint32_t ID_FMT = 0x20746d66; +static const uint32_t ID_DATA = 0x61746164; + +struct RiffWaveHeader { + uint32_t riff_id; + uint32_t riff_sz; + uint32_t wave_id; +}; + +struct ChunkHeader { + uint32_t id; + uint32_t sz; +}; + +struct ChunkFormat { + uint16_t audio_format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t byte_rate; + uint16_t block_align; + uint16_t bits_per_sample; +}; + +// this callback handler is called every time a buffer finishes playing +void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { + (void)bq; + (void)context; + assert(bq == bqPlayerBufferQueue); + assert(NULL == context); + audioplay::setPlaying(false); +} + +bool hasPlayer() { + return (engineObject != NULL && bqPlayerObject != NULL); +} + +// create the engine and output mix objects +void createEngine() { + SLresult result; + + // create engine + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the engine + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the engine interface, which is needed in order to create other objects + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // create output mix, with environmental reverb specified as a non-required interface + const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; + const SLboolean req[1] = {SL_BOOLEAN_FALSE}; + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the output mix + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; +} + +// create buffer queue audio player +void createBufferQueueAudioPlayer(const ChunkFormat* chunkFormat) { + SLresult result; + + // configure audio source + SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1}; + + SLDataFormat_PCM format_pcm = { + SL_DATAFORMAT_PCM, + chunkFormat->num_channels, + chunkFormat->sample_rate * 1000, // convert to milliHz + chunkFormat->bits_per_sample, + 16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + SLDataSource audioSrc = {&loc_bufq, &format_pcm}; + + // configure audio sink + SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; + SLDataSink audioSnk = {&loc_outmix, NULL}; + + // create audio player + const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME}; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, + 2, ids, req); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the player + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the play interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, + &bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + +#if 0 // mute/solo is not supported for sources that are known to be mono, as this is + // get the mute/solo interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_MUTESOLO, &bqPlayerMuteSolo); + assert(SL_RESULT_SUCCESS == result); + (void)result; +#endif + + // get the volume interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // set the player's state to playing + audioplay::setPlaying(true); + CHATTY("Created buffer queue player: %p", bqPlayerBufferQueue); +} + +} // namespace + +void create() { + createEngine(); +} + +bool playClip(const uint8_t* buf, int size) { + // Parse the WAV header + nextBuffer = buf; + nextSize = size; + const RiffWaveHeader* wavHeader = (const RiffWaveHeader*)buf; + if (nextSize < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || + (wavHeader->wave_id != ID_WAVE)) { + ALOGE("Error: audio file is not a riff/wave file\n"); + return false; + } + nextBuffer += sizeof(*wavHeader); + nextSize -= sizeof(*wavHeader); + + const ChunkFormat* chunkFormat = nullptr; + while (true) { + const ChunkHeader* chunkHeader = (const ChunkHeader*)nextBuffer; + if (nextSize < sizeof(*chunkHeader)) { + ALOGE("EOF reading chunk headers"); + return false; + } + + nextBuffer += sizeof(*chunkHeader); + nextSize -= sizeof(*chunkHeader); + + bool endLoop = false; + switch (chunkHeader->id) { + case ID_FMT: + chunkFormat = (const ChunkFormat*)nextBuffer; + nextBuffer += chunkHeader->sz; + nextSize -= chunkHeader->sz; + break; + case ID_DATA: + /* Stop looking for chunks */ + endLoop = true; + break; + default: + /* Unknown chunk, skip bytes */ + nextBuffer += chunkHeader->sz; + nextSize -= chunkHeader->sz; + } + if (endLoop) { + break; + } + } + + if (!chunkFormat) { + ALOGE("format not found in WAV file"); + return false; + } + + // If this is the first clip, create the buffer based on this WAV's header. + // We assume all future clips with be in the same format. + if (bqPlayerBufferQueue == nullptr) { + createBufferQueueAudioPlayer(chunkFormat); + } + + assert(bqPlayerBufferQueue != nullptr); + assert(buf != nullptr); + + if (!hasPlayer()) { + ALOGD("cannot play clip %p without a player", buf); + return false; + } + + CHATTY("playClip on player %p: buf=%p size=%d", bqPlayerBufferQueue, buf, size); + + if (nextSize > 0) { + // here we only enqueue one buffer because it is a long clip, + // but for streaming playback we would typically enqueue at least 2 buffers to start + SLresult result; + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize); + if (SL_RESULT_SUCCESS != result) { + return false; + } + audioplay::setPlaying(true); + } + + return true; +} + +// set the playing state for the buffer queue audio player +void setPlaying(bool isPlaying) { + if (!hasPlayer()) return; + + SLresult result; + + if (NULL != bqPlayerPlay) { + // set the player's state + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, + isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + } + +} + +void destroy() { + // destroy buffer queue audio player object, and invalidate all associated interfaces + if (bqPlayerObject != NULL) { + CHATTY("destroying audio player"); + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + bqPlayerPlay = NULL; + bqPlayerBufferQueue = NULL; + bqPlayerMuteSolo = NULL; + bqPlayerVolume = NULL; + } + + // destroy output mix object, and invalidate all associated interfaces + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + // destroy engine object, and invalidate all associated interfaces + if (engineObject != NULL) { + CHATTY("destroying audio engine"); + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + engineEngine = NULL; + } +} + +} // namespace audioplay diff --git a/cmds/bootanimation/audioplay.h b/cmds/bootanimation/audioplay.h new file mode 100644 index 000000000000..bdc0a1c84b6e --- /dev/null +++ b/cmds/bootanimation/audioplay.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 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. + * + */ + +#ifndef AUDIOPLAY_H_ +#define AUDIOPLAY_H_ + +#include + +namespace audioplay { + +void create(); + +// Play a WAV pointed to by buf. All clips are assumed to be in the same format. +// playClip should not be called while a clip is still playing. +bool playClip(const uint8_t* buf, int size); +void setPlaying(bool isPlaying); +void destroy(); + +} + +#endif // AUDIOPLAY_H_ -- cgit v1.2.3-59-g8ed1b From 271674930a1f9020a291827a8ecb6ab68782c4fa Mon Sep 17 00:00:00 2001 From: Sai Kiran Korwar Date: Tue, 7 Jul 2015 20:00:06 +0530 Subject: BootAnimation: Use npot textures If non power of two textures are supported, then use npot texture for specifying the images supplied by vendor. This will reduce the memory footprint of the app by a considerable amount. Bug: 28865582 Bug: 30040263 Change-Id: I5cef98fb013318d60d8a648041c9665da68c0ff3 --- cmds/bootanimation/BootAnimation.cpp | 22 ++++++++++++++++++---- cmds/bootanimation/BootAnimation.h | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e849f4b8e9d3..f2f7008452bb 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -202,25 +202,25 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) switch (bitmap.colorType()) { case kN32_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, p); } break; case kRGB_565_SkColorType: - if (tw != w || th != h) { + if (!mUseNpotTextures && (tw != w || th != h)) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } break; @@ -695,6 +695,20 @@ bool BootAnimation::movie() mClockEnabled = false; } + // Check if npot textures are supported + mUseNpotTextures = false; + String8 gl_extensions; + const char* exts = reinterpret_cast(glGetString(GL_EXTENSIONS)); + if (!exts) { + glGetError(); + } else { + gl_extensions.setTo(exts); + if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) || + (gl_extensions.find("GL_OES_texture_npot") != -1)) { + mUseNpotTextures = true; + } + } + // Blend required to draw time on top of animation frames. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glShadeModel(GL_FLAT); diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index a093c9b87e75..85724f46d9d6 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -130,6 +130,7 @@ private: Texture mClock; int mWidth; int mHeight; + bool mUseNpotTextures = false; EGLDisplay mDisplay; EGLDisplay mContext; EGLDisplay mSurface; -- cgit v1.2.3-59-g8ed1b From 2fb30fb68e9acb121a3e0dae0cd2790b3a7cc17d Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Wed, 6 Jul 2016 16:16:20 -0400 Subject: Wait until end of BootAnimation to free looping textures glDeleteTextures causes a noticeable framerate hitch when switching to the outro. By the time looping is finished, the system has finished booting, so freeing the textures immediately is less beneficial. Bug: 29878551 Bug: 30040263 Change-Id: I5a404a6e18cd2cf64d6c94343f82fa5db8dfbab3 --- cmds/bootanimation/BootAnimation.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e849f4b8e9d3..abb8c04d8032 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -839,9 +839,13 @@ bool BootAnimation::playAnimation(const Animation& animation) break; } - // free the textures for this part + } + + // Free textures created for looping parts now that the animation is done. + for (const Animation::Part& part : animation.parts) { if (part.count != 1) { - for (size_t j=0 ; j Date: Tue, 12 Jul 2016 15:53:40 -0700 Subject: Don't show times before 2000-01-01 BUG=27802041 Change-Id: Iaa826e029c3b887e2bda6d12bc7c372d862ed320 --- cmds/bootanimation/BootAnimation.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index e849f4b8e9d3..b9860382f500 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -72,6 +72,8 @@ 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"; +// 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 const char EXIT_PROP_NAME[] = "service.bootanim.exit"; static const int ANIM_ENTRY_NAME_MAX = 256; @@ -912,8 +914,9 @@ bool BootAnimation::updateIsTimeAccurate() { clock_gettime(CLOCK_REALTIME, &now); // Match the Java timestamp format long long rtcNow = (now.tv_sec * 1000LL) + (now.tv_nsec / 1000000LL); - if (lastChangedTime > rtcNow - MAX_TIME_IN_PAST - && lastChangedTime < rtcNow + MAX_TIME_IN_FUTURE) { + if (ACCURATE_TIME_EPOCH < rtcNow + && lastChangedTime > (rtcNow - MAX_TIME_IN_PAST) + && lastChangedTime < (rtcNow + MAX_TIME_IN_FUTURE)) { mTimeIsAccurate = true; } } -- cgit v1.2.3-59-g8ed1b From a91a2d737586ebd0040129333055d8093899751b Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Tue, 12 Jul 2016 14:46:19 -0400 Subject: Fixes delay when playing first sound in BootAnimation audioplay is initialized with an example of the type of clip it will play. Also remove asserts and debug compile settings from BootAnimation. BUG:24800792 Change-Id: Icb78489417aee0549c340c746b25e57ccdb3427e --- cmds/bootanimation/Android.mk | 4 - cmds/bootanimation/BootAnimation.cpp | 15 ++-- cmds/bootanimation/audioplay.cpp | 152 ++++++++++++++++++++++------------- cmds/bootanimation/audioplay.h | 8 +- 4 files changed, 111 insertions(+), 68 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index 3cf13d908632..3a92b9e74144 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -36,8 +36,4 @@ ifdef TARGET_32_BIT_SURFACEFLINGER LOCAL_32_BIT_ONLY := true endif -# get asserts to work -APP_OPTIM := debug -LOCAL_CFLAGS += -UNDEBUG - include $(BUILD_EXECUTABLE) diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 4098772bba12..ebcc9ff0451f 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -588,7 +588,7 @@ bool BootAnimation::preloadZip(Animation& animation) return false; } - bool hasAudio = false; + Animation::Part* partWithAudio = NULL; ZipEntryRO entry; char name[ANIM_ENTRY_NAME_MAX]; while ((entry = zip->nextEntry(cookie)) != NULL) { @@ -612,10 +612,10 @@ bool BootAnimation::preloadZip(Animation& animation) if (map) { Animation::Part& part(animation.parts.editItemAt(j)); if (leaf == "audio.wav") { - hasAudio = true; // a part may have at most one audio file part.audioData = (uint8_t *)map->getDataPtr(); part.audioLength = map->getDataLength(); + partWithAudio = ∂ } else if (leaf == "trim.txt") { part.trimData.setTo((char const*)map->getDataPtr(), map->getDataLength()); @@ -666,9 +666,11 @@ bool BootAnimation::preloadZip(Animation& animation) } // Create and initialize audioplay if there is a wav file in any of the animations. - if (hasAudio) { + if (partWithAudio != NULL) { ALOGD("found audio.wav, creating playback engine"); - audioplay::create(); + if (!audioplay::create(partWithAudio->audioData, partWithAudio->audioLength)) { + return false; + } } zip->endIteration(cookie); @@ -904,7 +906,10 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) mLoadedFiles.add(animation->fileName); parseAnimationDesc(*animation); - preloadZip(*animation); + if (!preloadZip(*animation)) { + return NULL; + } + mLoadedFiles.remove(fn); return animation; diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp index e20ef0c7c6cc..8a5c2c6d229c 100644 --- a/cmds/bootanimation/audioplay.cpp +++ b/cmds/bootanimation/audioplay.cpp @@ -21,7 +21,6 @@ #define CHATTY ALOGD -#include #include #include @@ -80,8 +79,6 @@ struct ChunkFormat { void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) { (void)bq; (void)context; - assert(bq == bqPlayerBufferQueue); - assert(NULL == context); audioplay::setPlaying(false); } @@ -90,39 +87,56 @@ bool hasPlayer() { } // create the engine and output mix objects -void createEngine() { +bool createEngine() { SLresult result; // create engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("slCreateEngine failed with result %d", result); + return false; + } (void)result; // realize the engine result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl engine Realize failed with result %d", result); + return false; + } (void)result; // get the engine interface, which is needed in order to create other objects result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl engine GetInterface failed with result %d", result); + return false; + } (void)result; // create output mix, with environmental reverb specified as a non-required interface const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean req[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl engine CreateOutputMix failed with result %d", result); + return false; + } (void)result; // realize the output mix result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl outputMix Realize failed with result %d", result); + return false; + } (void)result; + + return true; } // create buffer queue audio player -void createBufferQueueAudioPlayer(const ChunkFormat* chunkFormat) { +bool createBufferQueueAudioPlayer(const ChunkFormat* chunkFormat) { SLresult result; // configure audio source @@ -148,83 +162,89 @@ void createBufferQueueAudioPlayer(const ChunkFormat* chunkFormat) { const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl CreateAudioPlayer failed with result %d", result); + return false; + } (void)result; // realize the player result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl player Realize failed with result %d", result); + return false; + } (void)result; // get the play interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl player GetInterface failed with result %d", result); + return false; + } (void)result; // get the buffer queue interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl playberBufferQueue GetInterface failed with result %d", result); + return false; + } (void)result; // register callback on the buffer queue result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); - assert(SL_RESULT_SUCCESS == result); - (void)result; - -#if 0 // mute/solo is not supported for sources that are known to be mono, as this is - // get the mute/solo interface - result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_MUTESOLO, &bqPlayerMuteSolo); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl bqPlayerBufferQueue RegisterCallback failed with result %d", result); + return false; + } (void)result; -#endif // get the volume interface result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); - assert(SL_RESULT_SUCCESS == result); + if (result != SL_RESULT_SUCCESS) { + ALOGE("sl volume GetInterface failed with result %d", result); + return false; + } (void)result; // set the player's state to playing audioplay::setPlaying(true); CHATTY("Created buffer queue player: %p", bqPlayerBufferQueue); + return true; } -} // namespace - -void create() { - createEngine(); -} - -bool playClip(const uint8_t* buf, int size) { - // Parse the WAV header - nextBuffer = buf; - nextSize = size; - const RiffWaveHeader* wavHeader = (const RiffWaveHeader*)buf; - if (nextSize < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || +bool parseClipBuf(const uint8_t* clipBuf, int clipBufSize, const ChunkFormat** oChunkFormat, + const uint8_t** oSoundBuf, unsigned* oSoundBufSize) { + *oSoundBuf = clipBuf; + *oSoundBufSize = clipBufSize; + *oChunkFormat = NULL; + const RiffWaveHeader* wavHeader = (const RiffWaveHeader*)*oSoundBuf; + if (*oSoundBufSize < sizeof(*wavHeader) || (wavHeader->riff_id != ID_RIFF) || (wavHeader->wave_id != ID_WAVE)) { ALOGE("Error: audio file is not a riff/wave file\n"); return false; } - nextBuffer += sizeof(*wavHeader); - nextSize -= sizeof(*wavHeader); + *oSoundBuf += sizeof(*wavHeader); + *oSoundBufSize -= sizeof(*wavHeader); - const ChunkFormat* chunkFormat = nullptr; while (true) { - const ChunkHeader* chunkHeader = (const ChunkHeader*)nextBuffer; - if (nextSize < sizeof(*chunkHeader)) { + const ChunkHeader* chunkHeader = (const ChunkHeader*)*oSoundBuf; + if (*oSoundBufSize < sizeof(*chunkHeader)) { ALOGE("EOF reading chunk headers"); return false; } - nextBuffer += sizeof(*chunkHeader); - nextSize -= sizeof(*chunkHeader); + *oSoundBuf += sizeof(*chunkHeader); + *oSoundBufSize -= sizeof(*chunkHeader); bool endLoop = false; switch (chunkHeader->id) { case ID_FMT: - chunkFormat = (const ChunkFormat*)nextBuffer; - nextBuffer += chunkHeader->sz; - nextSize -= chunkHeader->sz; + *oChunkFormat = (const ChunkFormat*)*oSoundBuf; + *oSoundBuf += chunkHeader->sz; + *oSoundBufSize -= chunkHeader->sz; break; case ID_DATA: /* Stop looking for chunks */ @@ -232,27 +252,49 @@ bool playClip(const uint8_t* buf, int size) { break; default: /* Unknown chunk, skip bytes */ - nextBuffer += chunkHeader->sz; - nextSize -= chunkHeader->sz; + *oSoundBuf += chunkHeader->sz; + *oSoundBufSize -= chunkHeader->sz; } if (endLoop) { break; } } - if (!chunkFormat) { + if (*oChunkFormat == NULL) { ALOGE("format not found in WAV file"); return false; } + return true; +} - // If this is the first clip, create the buffer based on this WAV's header. - // We assume all future clips with be in the same format. - if (bqPlayerBufferQueue == nullptr) { - createBufferQueueAudioPlayer(chunkFormat); +} // namespace + +bool create(const uint8_t* exampleClipBuf, int exampleClipBufSize) { + if (!createEngine()) { + return false; } - assert(bqPlayerBufferQueue != nullptr); - assert(buf != nullptr); + // Parse the example clip. + const ChunkFormat* chunkFormat; + const uint8_t* soundBuf; + unsigned soundBufSize; + if (!parseClipBuf(exampleClipBuf, exampleClipBufSize, &chunkFormat, &soundBuf, &soundBufSize)) { + return false; + } + + // Initialize the BufferQueue based on this clip's format. + if (!createBufferQueueAudioPlayer(chunkFormat)) { + return false; + } + return true; +} + +bool playClip(const uint8_t* buf, int size) { + // Parse the WAV header + const ChunkFormat* chunkFormat; + if (!parseClipBuf(buf, size, &chunkFormat, &nextBuffer, &nextSize)) { + return false; + } if (!hasPlayer()) { ALOGD("cannot play clip %p without a player", buf); @@ -285,8 +327,6 @@ void setPlaying(bool isPlaying) { // set the player's state result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, isPlaying ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED); - assert(SL_RESULT_SUCCESS == result); - (void)result; } } diff --git a/cmds/bootanimation/audioplay.h b/cmds/bootanimation/audioplay.h index bdc0a1c84b6e..0e5705af0ad0 100644 --- a/cmds/bootanimation/audioplay.h +++ b/cmds/bootanimation/audioplay.h @@ -22,10 +22,12 @@ namespace audioplay { -void create(); +// Initializes the engine with an example of the type of WAV clip to play. +// All buffers passed to playClip are assumed to be in the same format. +bool create(const uint8_t* exampleClipBuf, int exampleClipBufSize); -// Play a WAV pointed to by buf. All clips are assumed to be in the same format. -// playClip should not be called while a clip is still playing. +// Plays a WAV contained in buf. +// Should not be called while a clip is still playing. bool playClip(const uint8_t* buf, int size); void setPlaying(bool isPlaying); void destroy(); -- cgit v1.2.3-59-g8ed1b From 305087991d1b88d98c5e5e03fcf5a6a6e93ad356 Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Fri, 22 Jul 2016 17:04:21 -0400 Subject: BootAnimation system property to play sound. Optional system property "persist.sys.bootanim.play_sound" If nonexistent, default behavior plays the sound. Bug: 30690353 Change-Id: Ie5ed456d891632be5ec8be255ac7c1193753c741 --- cmds/bootanimation/BootAnimation.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index ebcc9ff0451f..495e56ce64b3 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -75,6 +75,7 @@ static const char ACCURATE_TIME_FLAG_FILE_PATH[] = "/data/system/time/time_is_ac // 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 const char EXIT_PROP_NAME[] = "service.bootanim.exit"; +static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; static const int ANIM_ENTRY_NAME_MAX = 256; // --------------------------------------------------------------------------- @@ -778,8 +779,12 @@ bool BootAnimation::playAnimation(const Animation& animation) // only play audio file the first time we animate the part if (r == 0 && part.audioData) { - ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); - audioplay::playClip(part.audioData, part.audioLength); + // Read the system property to see if we should play the sound. + // If not present, default to playing it. + if (property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); + } } glClearColor( -- cgit v1.2.3-59-g8ed1b From 290c4350edc66de5663e27c54e694c8ee8cf0e0e Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Tue, 9 Aug 2016 14:35:10 -0400 Subject: Suppress Boot sound for non-standard scenarios. Blacklisted bootreasons will not play a sound (e.g. "kernel_panic") If boot has already completed, assume this is a runtime restart and skip the sound. Bug: 30654343 Change-Id: I41b1829a93c0d9a63c69aea2d1614eaa18b72230 --- cmds/bootanimation/BootAnimation.cpp | 45 ++++++++++++++++++++++++++++++------ cmds/bootanimation/BootAnimation.h | 2 ++ 2 files changed, 40 insertions(+), 7 deletions(-) (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 495e56ce64b3..a2d34e42f298 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -77,12 +77,23 @@ static const long long ACCURATE_TIME_EPOCH = 946684800000; static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; static const int ANIM_ENTRY_NAME_MAX = 256; +static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed"; +static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason"; +// bootreasons list in "system/core/bootstat/bootstat.cpp". +static const std::vector PLAY_SOUND_BOOTREASON_BLACKLIST { + "kernel_panic", + "Panic", + "Watchdog", +}; // --------------------------------------------------------------------------- BootAnimation::BootAnimation() : Thread(false), mClockEnabled(true), mTimeIsAccurate(false), mTimeCheckThread(NULL) { mSession = new SurfaceComposerClient(); + + // If the system has already booted, the animation is not being used for a boot. + mSystemBoot = !property_get_bool(BOOT_COMPLETED_PROP_NAME, 0); } BootAnimation::~BootAnimation() {} @@ -778,13 +789,9 @@ bool BootAnimation::playAnimation(const Animation& animation) break; // only play audio file the first time we animate the part - if (r == 0 && part.audioData) { - // Read the system property to see if we should play the sound. - // If not present, default to playing it. - if (property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { - ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); - audioplay::playClip(part.audioData, part.audioLength); - } + if (r == 0 && part.audioData && playSoundsAllowed()) { + ALOGD("playing clip for part%d, size=%d", (int) i, part.audioLength); + audioplay::playClip(part.audioData, part.audioLength); } glClearColor( @@ -920,6 +927,30 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) return animation; } +bool BootAnimation::playSoundsAllowed() const { + // Only play sounds for system boots, not runtime restarts. + if (!mSystemBoot) { + return false; + } + + // Read the system property to see if we should play the sound. + // If it's not present, default to allowed. + if (!property_get_bool(PLAY_SOUND_PROP_NAME, 1)) { + return false; + } + + // Don't play sounds if this is a reboot due to an error. + char bootreason[PROPERTY_VALUE_MAX]; + if (property_get(BOOTREASON_PROP_NAME, bootreason, nullptr) > 0) { + for (const auto& str : PLAY_SOUND_BOOTREASON_BLACKLIST) { + if (strcasecmp(str.c_str(), bootreason) == 0) { + return false; + } + } + } + return true; +} + bool BootAnimation::updateIsTimeAccurate() { static constexpr long long MAX_TIME_IN_PAST = 60000LL * 60LL * 24LL * 30LL; // 30 days static constexpr long long MAX_TIME_IN_FUTURE = 60000LL * 90LL; // 90 minutes diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index a53216eb0310..fd497a362fc7 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -120,6 +120,7 @@ private: void releaseAnimation(Animation*) const; bool parseAnimationDesc(Animation&); bool preloadZip(Animation &animation); + bool playSoundsAllowed() const; void checkExit(); @@ -137,6 +138,7 @@ private: sp mFlingerSurface; bool mClockEnabled; bool mTimeIsAccurate; + bool mSystemBoot; String8 mZipFileName; SortedVector mLoadedFiles; sp mTimeCheckThread; -- cgit v1.2.3-59-g8ed1b From 0e3d2ab6d2988a1ae70d13d6d77a0f8109eb66e1 Mon Sep 17 00:00:00 2001 From: Damien Bargiacchi Date: Mon, 29 Aug 2016 04:11:19 -0700 Subject: Allow custom fonts in the boot animaiton zip file Change the font format to be a 16x6 grid of characters Bug: 29580875 Change-Id: Ia468307cb9770436e8ae865c91acda23a71bde05 --- cmds/bootanimation/BootAnimation.cpp | 223 ++++++++++++++++++++++++---------- cmds/bootanimation/BootAnimation.h | 24 +++- core/res/assets/images/clock64.png | Bin 6035 -> 0 bytes core/res/assets/images/clock_font.png | Bin 0 -> 12142 bytes 4 files changed, 181 insertions(+), 66 deletions(-) delete mode 100644 core/res/assets/images/clock64.png create mode 100644 core/res/assets/images/clock_font.png (limited to 'cmds/bootanimation/BootAnimation.cpp') diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index a2d34e42f298..2fa1ee6b3869 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -68,15 +68,25 @@ static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootani 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"; // 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 PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; static const int ANIM_ENTRY_NAME_MAX = 256; +static constexpr size_t TEXT_POS_LEN_MAX = 16; static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed"; static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason"; // bootreasons list in "system/core/bootstat/bootstat.cpp". @@ -175,15 +185,14 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + return NO_ERROR; } -status_t BootAnimation::initTexture(const Animation::Frame& frame) +status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { - //StopWatch watch("blah"); - SkBitmap bitmap; - SkMemoryStream stream(frame.map->getDataPtr(), frame.map->getDataLength()); + SkMemoryStream stream(map->getDataPtr(), map->getDataLength()); SkImageDecoder* codec = SkImageDecoder::Factory(&stream); if (codec != NULL) { codec->setDitherImage(false); @@ -196,7 +205,7 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) // 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. - delete frame.map; + delete map; // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. @@ -242,6 +251,9 @@ status_t BootAnimation::initTexture(const Animation::Frame& frame) glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); + *width = w; + *height = h; + return NO_ERROR; } @@ -404,7 +416,6 @@ bool BootAnimation::android() return false; } - void BootAnimation::checkExit() { // Allow surface flinger to gracefully request shutdown char value[PROPERTY_VALUE_MAX]; @@ -415,6 +426,47 @@ void BootAnimation::checkExit() { } } +bool BootAnimation::validClock(const Animation::Part& part) { + return part.clockPosX != TEXT_MISSING_VALUE && part.clockPosY != TEXT_MISSING_VALUE; +} + +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 @@ -461,69 +513,105 @@ static bool readFile(ZipFileRO* zip, const char* name, String8& outString) return true; } -// The time glyphs are stored in a single image of height 64 pixels. Each digit is 40 pixels wide, -// and the colon character is half that at 20 pixels. The glyph order is '0123456789:'. -// We render 24 hour time. -void BootAnimation::drawTime(const Texture& clockTex, const int yPos) { - static constexpr char TIME_FORMAT[] = "%H:%M"; - static constexpr int TIME_LENGTH = sizeof(TIME_FORMAT); +// 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; - static constexpr int DIGIT_HEIGHT = 64; - static constexpr int DIGIT_WIDTH = 40; - static constexpr int COLON_WIDTH = DIGIT_WIDTH / 2; - static constexpr int TIME_WIDTH = (DIGIT_WIDTH * 4) + COLON_WIDTH; + if (font->map != nullptr) { + glGenTextures(1, &font->texture.name); + glBindTexture(GL_TEXTURE_2D, font->texture.name); - if (clockTex.h < DIGIT_HEIGHT || clockTex.w < (10 * DIGIT_WIDTH + COLON_WIDTH)) { - ALOGE("Clock texture is too small; abandoning boot animation clock"); - mClockEnabled = false; - return; + status = initTexture(font->map, &font->texture.w, &font->texture.h); + + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } else if (fallback != nullptr) { + status = initTexture(&font->texture, mAssets, fallback); + } else { + return NO_INIT; } - time_t rawtime; - time(&rawtime); - struct tm* timeInfo = localtime(&rawtime); + if (status == NO_ERROR) { + font->char_width = font->texture.w / FONT_NUM_COLS; + font->char_height = font->texture.h / FONT_NUM_ROWS / 2; // There are bold and regular rows + } - char timeBuff[TIME_LENGTH]; - size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo); + return status; +} - if (length != TIME_LENGTH - 1) { - ALOGE("Couldn't format time; abandoning boot animation clock"); - mClockEnabled = false; - return; +void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) { + glEnable(GL_BLEND); // Allow us to draw on top of the animation + glBindTexture(GL_TEXTURE_2D, font.texture.name); + + const int len = strlen(str); + const int strWidth = font.char_width * len; + + if (*x == TEXT_CENTER_VALUE) { + *x = (mWidth - strWidth) / 2; + } else if (*x < 0) { + *x = mWidth + *x - strWidth; + } + if (*y == TEXT_CENTER_VALUE) { + *y = (mHeight - font.char_height) / 2; + } else if (*y < 0) { + *y = mHeight + *y - font.char_height; } - glEnable(GL_BLEND); // Allow us to draw on top of the animation - glBindTexture(GL_TEXTURE_2D, clockTex.name); + int cropRect[4] = { 0, 0, font.char_width, -font.char_height }; - int xPos = (mWidth - TIME_WIDTH) / 2; - int cropRect[4] = { 0, DIGIT_HEIGHT, DIGIT_WIDTH, -DIGIT_HEIGHT }; + for (int i = 0; i < len; i++) { + char c = str[i]; - for (int i = 0; i < TIME_LENGTH - 1; i++) { - char c = timeBuff[i]; - int width = DIGIT_WIDTH; - int pos = c - '0'; // Position in the character list - if (pos < 0 || pos > 10) { - continue; - } - if (c == ':') { - width = COLON_WIDTH; + if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) { + c = '?'; } // Crop the texture to only the pixels in the current glyph - int left = pos * DIGIT_WIDTH; - cropRect[0] = left; - cropRect[2] = width; + const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters + const int row = charPos / FONT_NUM_COLS; + const int col = charPos % FONT_NUM_COLS; + cropRect[0] = col * font.char_width; // Left of column + cropRect[1] = row * font.char_height * 2; // Top of row + // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line + cropRect[1] += bold ? 2 * font.char_height : font.char_height; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); - glDrawTexiOES(xPos, yPos, 0, width, DIGIT_HEIGHT); + glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height); - xPos += width; + *x += font.char_width; } glDisable(GL_BLEND); // Return to the animation's default behaviour glBindTexture(GL_TEXTURE_2D, 0); } +// We render 24 hour time. +void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) { + static constexpr char TIME_FORMAT[] = "%H:%M"; + static constexpr int TIME_LENGTH = 6; + + time_t rawtime; + time(&rawtime); + struct tm* timeInfo = localtime(&rawtime); + + char timeBuff[TIME_LENGTH]; + size_t length = strftime(timeBuff, TIME_LENGTH, TIME_FORMAT, timeInfo); + + if (length != TIME_LENGTH - 1) { + ALOGE("Couldn't format time; abandoning boot animation clock"); + mClockEnabled = false; + return; + } + + int x = xPos; + int y = yPos; + drawText(timeBuff, font, false, &x, &y); +} + bool BootAnimation::parseAnimationDesc(Animation& animation) { String8 desString; @@ -544,9 +632,10 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) int height = 0; int count = 0; int pause = 0; - int clockPosY = -1; 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) { @@ -554,15 +643,15 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) animation.width = width; animation.height = height; animation.fps = fps; - } else if (sscanf(l, " %c %d %d %s #%6s %d", - &pathType, &count, &pause, path, color, &clockPosY) >= 4) { - // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPosY=%d", pathType, count, pause, path, color, clockPosY); + } else if (sscanf(l, " %c %d %d %s #%6s %16s %16s", + &pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) { + //ALOGD("> 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.clockPosY = clockPosY; part.audioData = NULL; part.animation = NULL; if (!parseColor(color, part.backgroundColor)) { @@ -571,6 +660,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.backgroundColor[1] = 0.0f; part.backgroundColor[2] = 0.0f; } + parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY); animation.parts.add(part); } else if (strcmp(l, "$SYSTEM") == 0) { @@ -614,6 +704,14 @@ bool BootAnimation::preloadZip(Animation& animation) const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); 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++) { if (path == animation.parts[j].path) { uint16_t method; @@ -698,7 +796,7 @@ bool BootAnimation::movie() bool anyPartHasClock = false; for (size_t i=0; i < animation->parts.size(); i++) { - if(animation->parts[i].clockPosY >= 0) { + if(validClock(animation->parts[i])) { anyPartHasClock = true; break; } @@ -736,10 +834,11 @@ bool BootAnimation::movie() glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - bool clockTextureInitialized = false; + bool clockFontInitialized = false; if (mClockEnabled) { - clockTextureInitialized = (initTexture(&mClock, mAssets, "images/clock64.png") == NO_ERROR); - mClockEnabled = clockTextureInitialized; + clockFontInitialized = + (initFont(&animation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR); + mClockEnabled = clockFontInitialized; } if (mClockEnabled && !updateIsTimeAccurate()) { @@ -756,8 +855,8 @@ bool BootAnimation::movie() releaseAnimation(animation); - if (clockTextureInitialized) { - glDeleteTextures(1, &mClock.name); + if (clockFontInitialized) { + glDeleteTextures(1, &animation->clockFont.texture.name); } return false; @@ -813,7 +912,8 @@ bool BootAnimation::playAnimation(const Animation& animation) glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - initTexture(frame); + int w, h; + initTexture(frame.map, &w, &h); } const int xc = animationX + frame.trimX; @@ -835,8 +935,8 @@ bool BootAnimation::playAnimation(const Animation& animation) // which is equivalent to mHeight - (yc + frame.trimHeight) glDrawTexiOES(xc, mHeight - (yc + frame.trimHeight), 0, frame.trimWidth, frame.trimHeight); - if (mClockEnabled && mTimeIsAccurate && part.clockPosY >= 0) { - drawTime(mClock, part.clockPosY); + if (mClockEnabled && mTimeIsAccurate && validClock(part)) { + drawClock(animation.clockFont, part.clockPosX, part.clockPosY); } eglSwapBuffers(mDisplay, mSurface); @@ -915,6 +1015,7 @@ BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) Animation *animation = new Animation; animation->fileName = fn; animation->zip = zip; + animation->clockFont.map = nullptr; mLoadedFiles.add(animation->fileName); parseAnimationDesc(*animation); diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index fd497a362fc7..42759f1acf0d 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -74,6 +74,13 @@ private: GLuint name; }; + struct Font { + FileMap* map; + Texture texture; + int char_width; + int char_height; + }; + struct Animation { struct Frame { String8 name; @@ -90,8 +97,12 @@ private: struct Part { int count; // The number of times this part should repeat, 0 for infinite int pause; // The number of frames to pause for at the end of this part - int clockPosY; // The y position of the clock, in pixels, from the bottom of the - // display (the clock is centred horizontally). -1 to disable the clock + int clockPosX; // The x position of the clock, in pixels. Positive values offset from + // the left of the screen, negative values offset from the right. + int clockPosY; // The y position of the clock, in pixels. Positive values offset from + // the bottom of the screen, negative values offset from the top. + // If either of the above are INT_MIN the clock is disabled, if INT_MAX + // the clock is centred on that axis. String8 path; String8 trimData; SortedVector frames; @@ -108,13 +119,17 @@ private: String8 audioConf; String8 fileName; ZipFileRO* zip; + Font clockFont; }; status_t initTexture(Texture* texture, AssetManager& asset, const char* name); - status_t initTexture(const Animation::Frame& frame); + status_t initTexture(FileMap* map, int* width, int* height); + status_t initFont(Font* font, const char* fallback); bool android(); bool movie(); - void drawTime(const Texture& clockTex, const int yPos); + void drawText(const char* str, const Font& font, bool bold, int* x, int* y); + void drawClock(const Font& font, const int xPos, const int yPos); + bool validClock(const Animation::Part& part); Animation* loadAnimation(const String8&); bool playAnimation(const Animation&); void releaseAnimation(Animation*) const; @@ -127,7 +142,6 @@ private: sp mSession; AssetManager mAssets; Texture mAndroid[2]; - Texture mClock; int mWidth; int mHeight; bool mUseNpotTextures = false; diff --git a/core/res/assets/images/clock64.png b/core/res/assets/images/clock64.png deleted file mode 100644 index 2e01e38f6d93..000000000000 Binary files a/core/res/assets/images/clock64.png and /dev/null differ diff --git a/core/res/assets/images/clock_font.png b/core/res/assets/images/clock_font.png new file mode 100644 index 000000000000..be927ae2612f Binary files /dev/null and b/core/res/assets/images/clock_font.png differ -- cgit v1.2.3-59-g8ed1b