diff options
Diffstat (limited to 'cmds/bootanimation')
-rw-r--r-- | cmds/bootanimation/Android.bp | 3 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimation.cpp | 46 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimation.h | 1 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimationUtil.cpp | 43 | ||||
-rw-r--r-- | cmds/bootanimation/BootAnimationUtil.h | 7 | ||||
-rw-r--r-- | cmds/bootanimation/audioplay.cpp | 81 | ||||
-rw-r--r-- | cmds/bootanimation/audioplay.h | 5 | ||||
-rw-r--r-- | cmds/bootanimation/bootanimation_main.cpp | 111 | ||||
-rw-r--r-- | cmds/bootanimation/iot/Android.bp | 49 | ||||
-rw-r--r-- | cmds/bootanimation/iot/BootAction.cpp | 33 | ||||
-rw-r--r-- | cmds/bootanimation/iot/BootAction.h | 9 | ||||
-rw-r--r-- | cmds/bootanimation/iot/BootParameters.cpp | 179 | ||||
-rw-r--r-- | cmds/bootanimation/iot/BootParameters.h | 49 | ||||
-rw-r--r-- | cmds/bootanimation/iot/BootParameters_test.cpp | 263 | ||||
-rw-r--r-- | cmds/bootanimation/iot/iotbootanimation_main.cpp | 14 |
15 files changed, 671 insertions, 222 deletions
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp index 60a1cfbe9dd7..31bd612cc2c7 100644 --- a/cmds/bootanimation/Android.bp +++ b/cmds/bootanimation/Android.bp @@ -44,8 +44,11 @@ cc_binary { product_is_iot: { shared_libs: [ "libandroidthings", + "libandroidthings_protos", "libchrome", + "libprotobuf-cpp-lite", ], + static_libs: ["libjsoncpp"], srcs: [ "iot/iotbootanimation_main.cpp", "iot/BootAction.cpp", diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index ed6c25dc49c3..5dcb392b002d 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -114,7 +114,7 @@ BootAnimation::BootAnimation(sp<Callbacks> callbacks) void BootAnimation::onFirstRef() { status_t err = mSession->linkToComposerDeath(this); - ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); + SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err)); if (err == NO_ERROR) { run("BootAnimation", PRIORITY_DISPLAY); } @@ -128,7 +128,7 @@ sp<SurfaceComposerClient> BootAnimation::session() const { void BootAnimation::binderDied(const wp<IBinder>&) { // woah, surfaceflinger died! - ALOGD("SurfaceFlinger died, exiting..."); + SLOGD("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. @@ -360,7 +360,7 @@ bool BootAnimation::threadLoop() bool BootAnimation::android() { - ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", + SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png"); @@ -508,14 +508,14 @@ static bool parseColor(const char str[7], float color[3]) { static bool readFile(ZipFileRO* zip, const char* name, String8& outString) { ZipEntryRO entry = zip->findEntryByName(name); - ALOGE_IF(!entry, "couldn't find %s", name); + SLOGE_IF(!entry, "couldn't find %s", name); if (!entry) { return false; } FileMap* entryMap = zip->createEntryFileMap(entry); zip->releaseEntry(entry); - ALOGE_IF(!entryMap, "entryMap is null"); + SLOGE_IF(!entryMap, "entryMap is null"); if (!entryMap) { return false; } @@ -616,7 +616,7 @@ void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) size_t length = strftime(timeBuff, TIME_LENGTH, timeFormat, timeInfo); if (length != TIME_LENGTH - 1) { - ALOGE("Couldn't format time; abandoning boot animation clock"); + SLOGE("Couldn't format time; abandoning boot animation clock"); mClockEnabled = false; return; } @@ -654,13 +654,13 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) char pathType; if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) { - // ALOGD("> w=%d, h=%d, fps=%d", width, height, fps); + // SLOGD("> w=%d, h=%d, fps=%d", width, height, fps); animation.width = width; animation.height = height; animation.fps = fps; } 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", + //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'; @@ -670,7 +670,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) part.audioData = NULL; part.animation = NULL; if (!parseColor(color, part.backgroundColor)) { - ALOGE("> invalid color '#%s'", color); + SLOGE("> invalid color '#%s'", color); part.backgroundColor[0] = 0.0f; part.backgroundColor[1] = 0.0f; part.backgroundColor[2] = 0.0f; @@ -679,7 +679,7 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) animation.parts.add(part); } else if (strcmp(l, "$SYSTEM") == 0) { - // ALOGD("> SYSTEM"); + // SLOGD("> SYSTEM"); Animation::Part part; part.playUntilComplete = false; part.count = 1; @@ -710,7 +710,7 @@ bool BootAnimation::preloadZip(Animation& animation) 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"); + SLOGE("Error fetching entry file name"); continue; } @@ -754,7 +754,7 @@ bool BootAnimation::preloadZip(Animation& animation) } } } else { - ALOGE("bootanimation.zip is compressed; must be only stored"); + SLOGE("bootanimation.zip is compressed; must be only stored"); } } } @@ -782,7 +782,7 @@ bool BootAnimation::preloadZip(Animation& animation) frame.trimX = x; frame.trimY = y; } else { - ALOGE("Error parsing trim.txt, line: %s", lineStr); + SLOGE("Error parsing trim.txt, line: %s", lineStr); break; } } @@ -860,12 +860,12 @@ bool BootAnimation::movie() mTimeCheckThread = nullptr; } - releaseAnimation(animation); - if (clockFontInitialized) { glDeleteTextures(1, &animation->clockFont.texture.name); } + releaseAnimation(animation); + return false; } @@ -876,7 +876,7 @@ bool BootAnimation::playAnimation(const Animation& animation) const int animationX = (mWidth - animation.width) / 2; const int animationY = (mHeight - animation.height) / 2; - ALOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", + SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); for (size_t i=0 ; i<pcount ; i++) { const Animation::Part& part(animation.parts[i]); @@ -949,7 +949,7 @@ bool BootAnimation::playAnimation(const Animation& animation) nsecs_t now = systemTime(); nsecs_t delay = frameDuration - (now - lastFrame); - //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay)); + //SLOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay)); lastFrame = now; if (delay > 0) { @@ -1048,13 +1048,13 @@ void BootAnimation::releaseAnimation(Animation* animation) const BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) { if (mLoadedFiles.indexOf(fn) >= 0) { - ALOGE("File \"%s\" is already loaded. Cyclic ref is not allowed", + SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed", fn.string()); return NULL; } ZipFileRO *zip = ZipFileRO::open(fn); if (zip == NULL) { - ALOGE("Failed to open animation zip \"%s\": %s", + SLOGE("Failed to open animation zip \"%s\": %s", fn.string(), strerror(errno)); return NULL; } @@ -1143,7 +1143,7 @@ bool BootAnimation::TimeCheckThread::doThreadLoop() { if (pollResult == 0) { return true; } else if (pollResult < 0) { - ALOGE("Could not poll inotify events"); + SLOGE("Could not poll inotify events"); return false; } @@ -1152,7 +1152,7 @@ bool BootAnimation::TimeCheckThread::doThreadLoop() { if (length == 0) { return true; } else if (length < 0) { - ALOGE("Could not read inotify events"); + SLOGE("Could not read inotify events"); return false; } @@ -1183,7 +1183,7 @@ void BootAnimation::TimeCheckThread::addTimeDirWatch() { status_t BootAnimation::TimeCheckThread::readyToRun() { mInotifyFd = inotify_init(); if (mInotifyFd < 0) { - ALOGE("Could not initialize inotify fd"); + SLOGE("Could not initialize inotify fd"); return NO_INIT; } @@ -1191,7 +1191,7 @@ status_t BootAnimation::TimeCheckThread::readyToRun() { if (mSystemWd < 0) { close(mInotifyFd); mInotifyFd = -1; - ALOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); + SLOGE("Could not add watch for %s", SYSTEM_DATA_DIR_PATH); return NO_INIT; } diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index 4fd5c0ef5f28..04d4f9a6fd06 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -22,6 +22,7 @@ #include <androidfw/AssetManager.h> #include <utils/Thread.h> +#include <binder/IBinder.h> #include <EGL/egl.h> #include <GLES/gl.h> diff --git a/cmds/bootanimation/BootAnimationUtil.cpp b/cmds/bootanimation/BootAnimationUtil.cpp index 7718daf61d81..1e417e938359 100644 --- a/cmds/bootanimation/BootAnimationUtil.cpp +++ b/cmds/bootanimation/BootAnimationUtil.cpp @@ -16,14 +16,30 @@ #include "BootAnimationUtil.h" +#include <vector> #include <inttypes.h> #include <binder/IServiceManager.h> #include <cutils/properties.h> #include <utils/Log.h> #include <utils/SystemClock.h> +#include <android-base/properties.h> namespace android { +namespace { + +static constexpr char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; +static constexpr char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed"; +static constexpr char POWER_CTL_PROP_NAME[] = "sys.powerctl"; +static constexpr char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason"; +static const std::vector<std::string> PLAY_SOUND_BOOTREASON_BLACKLIST { + "kernel_panic", + "Panic", + "Watchdog", +}; + +} // namespace + bool bootAnimationDisabled() { char value[PROPERTY_VALUE_MAX]; @@ -58,4 +74,31 @@ void waitForSurfaceFlinger() { } } +bool playSoundsAllowed() { + // Only play sounds for system boots, not runtime restarts. + if (android::base::GetBoolProperty(BOOT_COMPLETED_PROP_NAME, false)) { + return false; + } + // no audio while shutting down + if (!android::base::GetProperty(POWER_CTL_PROP_NAME, "").empty()) { + 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; +} + } // namespace android diff --git a/cmds/bootanimation/BootAnimationUtil.h b/cmds/bootanimation/BootAnimationUtil.h index 60987cd1ccd1..1e1140a51763 100644 --- a/cmds/bootanimation/BootAnimationUtil.h +++ b/cmds/bootanimation/BootAnimationUtil.h @@ -14,6 +14,9 @@ * limitations under the License. */ +#ifndef ANDROID_BOOTANIMATION_UTIL_H +#define ANDROID_BOOTANIMATION_UTIL_H + namespace android { // Returns true if boot animation is disabled. @@ -22,4 +25,8 @@ bool bootAnimationDisabled(); // Waits until the surface flinger is up. void waitForSurfaceFlinger(); +// Returns whether sounds should be played during current boot. +bool playSoundsAllowed(); } // namespace android + +#endif // ANDROID_BOOTANIMATION_UTIL_H diff --git a/cmds/bootanimation/audioplay.cpp b/cmds/bootanimation/audioplay.cpp index c546072e733a..874aab08862e 100644 --- a/cmds/bootanimation/audioplay.cpp +++ b/cmds/bootanimation/audioplay.cpp @@ -17,22 +17,27 @@ // cribbed from samples/native-audio -#include "audioplay.h" - #define CHATTY ALOGD #define LOG_TAG "audioplay" +#include "audioplay.h" + #include <string.h> #include <utils/Log.h> +#include <utils/threads.h> // for native audio #include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h> +#include "BootAnimationUtil.h" + namespace audioplay { namespace { +using namespace android; + // engine interfaces static SLObjectItf engineObject = NULL; static SLEngineItf engineEngine; @@ -305,6 +310,74 @@ bool parseClipBuf(const uint8_t* clipBuf, int clipBufSize, const ChunkFormat** o return true; } +class InitAudioThread : public Thread { +public: + InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength) + : Thread(false), + mExampleAudioData(exampleAudioData), + mExampleAudioLength(exampleAudioLength) {} +private: + virtual bool threadLoop() { + audioplay::create(mExampleAudioData, mExampleAudioLength); + // Exit immediately + return false; + } + + uint8_t* mExampleAudioData; + int mExampleAudioLength; +}; + +// Typedef to aid readability. +typedef android::BootAnimation::Animation Animation; + +class AudioAnimationCallbacks : public android::BootAnimation::Callbacks { +public: + void init(const Vector<Animation::Part>& parts) override { + const Animation::Part* partWithAudio = nullptr; + for (const Animation::Part& part : parts) { + if (part.audioData != nullptr) { + partWithAudio = ∂ + break; + } + } + + if (partWithAudio == nullptr) { + return; + } + + ALOGD("found audio.wav, creating playback engine"); + // The audioData is used to initialize the audio system. Different data + // can be played later for other parts BUT the assumption is that they + // will all be the same format and only the format of this audioData + // will work correctly. + initAudioThread = new InitAudioThread(partWithAudio->audioData, + partWithAudio->audioLength); + initAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL); + }; + + void playPart(int partNumber, const Animation::Part& part, int playNumber) override { + // only play audio file the first time we animate the part + if (playNumber == 0 && part.audioData && playSoundsAllowed()) { + ALOGD("playing clip for part%d, size=%d", + partNumber, part.audioLength); + // Block until the audio engine is finished initializing. + if (initAudioThread != nullptr) { + initAudioThread->join(); + } + audioplay::playClip(part.audioData, part.audioLength); + } + }; + + void shutdown() override { + // we've finally played everything we're going to play + audioplay::setPlaying(false); + audioplay::destroy(); + }; + +private: + sp<InitAudioThread> initAudioThread = nullptr; +}; + } // namespace bool create(const uint8_t* exampleClipBuf, int exampleClipBufSize) { @@ -397,4 +470,8 @@ void destroy() { } } +sp<BootAnimation::Callbacks> createAnimationCallbacks() { + return new AudioAnimationCallbacks(); +} + } // namespace audioplay diff --git a/cmds/bootanimation/audioplay.h b/cmds/bootanimation/audioplay.h index 0e5705af0ad0..4704a702d50b 100644 --- a/cmds/bootanimation/audioplay.h +++ b/cmds/bootanimation/audioplay.h @@ -20,6 +20,8 @@ #include <string.h> +#include "BootAnimation.h" + namespace audioplay { // Initializes the engine with an example of the type of WAV clip to play. @@ -32,6 +34,9 @@ bool playClip(const uint8_t* buf, int size); void setPlaying(bool isPlaying); void destroy(); +// Generates callbacks to integrate the audioplay system with the BootAnimation. +android::sp<android::BootAnimation::Callbacks> createAnimationCallbacks(); + } #endif // AUDIOPLAY_H_ diff --git a/cmds/bootanimation/bootanimation_main.cpp b/cmds/bootanimation/bootanimation_main.cpp index 8501982d071c..a52a5e92a840 100644 --- a/cmds/bootanimation/bootanimation_main.cpp +++ b/cmds/bootanimation/bootanimation_main.cpp @@ -26,8 +26,6 @@ #include <sys/resource.h> #include <utils/Log.h> #include <utils/SystemClock.h> -#include <utils/threads.h> -#include <android-base/properties.h> #include "BootAnimation.h" #include "BootAnimationUtil.h" @@ -35,113 +33,6 @@ using namespace android; -// --------------------------------------------------------------------------- - -namespace { - -// Create a typedef for readability. -typedef android::BootAnimation::Animation Animation; - -static const char PLAY_SOUND_PROP_NAME[] = "persist.sys.bootanim.play_sound"; -static const char BOOT_COMPLETED_PROP_NAME[] = "sys.boot_completed"; -static const char POWER_CTL_PROP_NAME[] = "sys.powerctl"; -static const char BOOTREASON_PROP_NAME[] = "ro.boot.bootreason"; -static const std::vector<std::string> PLAY_SOUND_BOOTREASON_BLACKLIST { - "kernel_panic", - "Panic", - "Watchdog", -}; - -class InitAudioThread : public Thread { -public: - InitAudioThread(uint8_t* exampleAudioData, int exampleAudioLength) - : Thread(false), - mExampleAudioData(exampleAudioData), - mExampleAudioLength(exampleAudioLength) {} -private: - virtual bool threadLoop() { - audioplay::create(mExampleAudioData, mExampleAudioLength); - // Exit immediately - return false; - } - - uint8_t* mExampleAudioData; - int mExampleAudioLength; -}; - -bool playSoundsAllowed() { - // Only play sounds for system boots, not runtime restarts. - if (android::base::GetBoolProperty(BOOT_COMPLETED_PROP_NAME, false)) { - return false; - } - // no audio while shutting down - if (!android::base::GetProperty(POWER_CTL_PROP_NAME, "").empty()) { - 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; -} - -class AudioAnimationCallbacks : public android::BootAnimation::Callbacks { -public: - void init(const Vector<Animation::Part>& parts) override { - const Animation::Part* partWithAudio = nullptr; - for (const Animation::Part& part : parts) { - if (part.audioData != nullptr) { - partWithAudio = ∂ - } - } - - if (partWithAudio == nullptr) { - return; - } - - ALOGD("found audio.wav, creating playback engine"); - initAudioThread = new InitAudioThread(partWithAudio->audioData, - partWithAudio->audioLength); - initAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL); - }; - - void playPart(int partNumber, const Animation::Part& part, int playNumber) override { - // only play audio file the first time we animate the part - if (playNumber == 0 && part.audioData && playSoundsAllowed()) { - ALOGD("playing clip for part%d, size=%d", - partNumber, part.audioLength); - // Block until the audio engine is finished initializing. - if (initAudioThread != nullptr) { - initAudioThread->join(); - } - audioplay::playClip(part.audioData, part.audioLength); - } - }; - - void shutdown() override { - // we've finally played everything we're going to play - audioplay::setPlaying(false); - audioplay::destroy(); - }; - -private: - sp<InitAudioThread> initAudioThread = nullptr; -}; - -} // namespace - - int main() { setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY); @@ -156,7 +47,7 @@ int main() waitForSurfaceFlinger(); // create the boot animation object - sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks()); + sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks()); ALOGV("Boot animation set up. Joining pool."); IPCThreadState::self()->joinThreadPool(); diff --git a/cmds/bootanimation/iot/Android.bp b/cmds/bootanimation/iot/Android.bp new file mode 100644 index 000000000000..1f248adcb9e1 --- /dev/null +++ b/cmds/bootanimation/iot/Android.bp @@ -0,0 +1,49 @@ +// Copyright (C) 2018 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. +// + +// libbootanimation_iot_test +// =========================================================== +cc_test { + name: "libbootanimation_iot_test", + cflags: [ + "-Wall", + "-Werror", + "-Wunused", + "-Wunreachable-code", + ], + + shared_libs: [ + "libandroidthings", + "libandroidthings_protos", + "libbase", + "libchrome", + "liblog", + "libprotobuf-cpp-lite", + ], + + static_libs: ["libjsoncpp"], + + srcs: [ + "BootParameters.cpp", + "BootParameters_test.cpp", + ], + + enabled: false, + product_variables: { + product_is_iot: { + enabled: true, + }, + }, +} diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp index fa797444d569..8b55147110bc 100644 --- a/cmds/bootanimation/iot/BootAction.cpp +++ b/cmds/bootanimation/iot/BootAction.cpp @@ -32,7 +32,7 @@ BootAction::~BootAction() { } bool BootAction::init(const std::string& libraryPath, - const std::vector<ABootActionParameter>& parameters) { + const std::unique_ptr<BootParameters>& bootParameters) { APeripheralManagerClient* client = nullptr; ALOGD("Connecting to peripheralmanager"); // Wait for peripheral manager to come up. @@ -77,9 +77,32 @@ bool BootAction::init(const std::string& libraryPath, mLibStartPart = reinterpret_cast<libStartPart>(loaded); } - ALOGD("Entering boot_action_init"); - bool result = mLibInit(parameters.data(), parameters.size()); - ALOGD("Returned from boot_action_init"); + // SilentBoot is considered optional, if it isn't exported by the library + // and the boot is silent, no method is called. + loaded = nullptr; + if (!loadSymbol("boot_action_silent_boot", &loaded) || loaded == nullptr) { + ALOGW("No boot_action_silent_boot found, boot action will not be " + "executed during a silent boot."); + } else { + mLibSilentBoot = reinterpret_cast<libInit>(loaded); + } + + bool result = true; + const auto& parameters = bootParameters->getParameters(); + if (bootParameters->isSilentBoot()) { + if (mLibSilentBoot != nullptr) { + ALOGD("Entering boot_action_silent_boot"); + result = mLibSilentBoot(parameters.data(), parameters.size()); + ALOGD("Returned from boot_action_silent_boot"); + } else { + ALOGW("Skipping missing boot_action_silent_boot"); + } + } else { + ALOGD("Entering boot_action_init"); + result = mLibInit(parameters.data(), parameters.size()); + ALOGD("Returned from boot_action_init"); + } + return result; } @@ -99,7 +122,7 @@ void BootAction::shutdown() { bool BootAction::loadSymbol(const char* symbol, void** loaded) { *loaded = dlsym(mLibHandle, symbol); - if (loaded == nullptr) { + if (*loaded == nullptr) { ALOGE("Unable to load symbol : %s :: %s", symbol, dlerror()); return false; } diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h index 5e2495fe6c51..7119c35db0f9 100644 --- a/cmds/bootanimation/iot/BootAction.h +++ b/cmds/bootanimation/iot/BootAction.h @@ -20,6 +20,8 @@ #include <string> #include <vector> +#include "BootParameters.h" + #include <boot_action/boot_action.h> // libandroidthings native API. #include <utils/RefBase.h> @@ -31,7 +33,7 @@ public: // libraryPath is a fully qualified path to the target .so library. bool init(const std::string& libraryPath, - const std::vector<ABootActionParameter>& parameters); + const std::unique_ptr<BootParameters>& bootParameters); // The animation is going to start playing partNumber for the playCount'th // time, update the action as needed. @@ -45,7 +47,7 @@ public: private: typedef bool (*libInit)(const ABootActionParameter* parameters, - size_t num_parameters); + size_t numParameters); typedef void (*libStartPart)(int partNumber, int playNumber); typedef void (*libShutdown)(); @@ -55,6 +57,9 @@ private: libInit mLibInit = nullptr; libStartPart mLibStartPart = nullptr; libShutdown mLibShutdown = nullptr; + + // Called only if the boot is silent. + libInit mLibSilentBoot = nullptr; }; } // namespace android diff --git a/cmds/bootanimation/iot/BootParameters.cpp b/cmds/bootanimation/iot/BootParameters.cpp index da6ad0d1f08f..30a9b2895c44 100644 --- a/cmds/bootanimation/iot/BootParameters.cpp +++ b/cmds/bootanimation/iot/BootParameters.cpp @@ -18,45 +18,52 @@ #define LOG_TAG "BootParameters" +#include <errno.h> #include <fcntl.h> -#include <string> - #include <android-base/file.h> -#include <base/json/json_parser.h> -#include <base/json/json_reader.h> -#include <base/json/json_value_converter.h> +#include <json/json.h> #include <utils/Log.h> -using android::base::RemoveFileIfExists; using android::base::ReadFileToString; -using base::JSONReader; -using base::JSONValueConverter; -using base::Value; +using android::base::RemoveFileIfExists; +using android::base::WriteStringToFile; +using Json::ArrayIndex; +using Json::Reader; +using Json::Value; namespace android { namespace { -// Brightness and volume are stored as integer strings in next_boot.json. -// They are divided by this constant to produce the actual float values in -// range [0.0, 1.0]. This constant must match its counterpart in -// DeviceManager. -constexpr const float kFloatScaleFactor = 1000.0f; +// Keys for deprecated parameters. Devices that OTA from N to O and that used +// the hidden BootParameters API will store these in the JSON blob. To support +// the transition from N to O, these keys are mapped to the new parameters. +constexpr const char *kKeyLegacyVolume = "volume"; +constexpr const char *kKeyLegacyAnimationsDisabled = "boot_animation_disabled"; +constexpr const char *kKeyLegacyParamNames = "param_names"; +constexpr const char *kKeyLegacyParamValues = "param_values"; + +constexpr const char *kNextBootFile = "/data/misc/bootanimation/next_boot.proto"; +constexpr const char *kLastBootFile = "/data/misc/bootanimation/last_boot.proto"; -constexpr const char* kNextBootFile = "/data/misc/bootanimation/next_boot.json"; -constexpr const char* kLastBootFile = "/data/misc/bootanimation/last_boot.json"; +constexpr const char *kLegacyNextBootFile = "/data/misc/bootanimation/next_boot.json"; +constexpr const char *kLegacyLastBootFile = "/data/misc/bootanimation/last_boot.json"; -void swapBootConfigs() { - // rename() will fail if next_boot.json doesn't exist, so delete - // last_boot.json manually first. +void removeLegacyFiles() { std::string err; - if (!RemoveFileIfExists(kLastBootFile, &err)) - ALOGE("Unable to delete last boot file: %s", err.c_str()); + if (!RemoveFileIfExists(kLegacyLastBootFile, &err)) { + ALOGW("Unable to delete %s: %s", kLegacyLastBootFile, err.c_str()); + } - if (rename(kNextBootFile, kLastBootFile) && errno != ENOENT) - ALOGE("Unable to swap boot files: %s", strerror(errno)); + err.clear(); + if (!RemoveFileIfExists(kLegacyNextBootFile, &err)) { + ALOGW("Unable to delete %s: %s", kLegacyNextBootFile, err.c_str()); + } +} +void createNextBootFile() { + errno = 0; int fd = open(kNextBootFile, O_CREAT, DEFFILEMODE); if (fd == -1) { ALOGE("Unable to create next boot file: %s", strerror(errno)); @@ -71,54 +78,120 @@ void swapBootConfigs() { } // namespace -BootParameters::SavedBootParameters::SavedBootParameters() - : brightness(-kFloatScaleFactor), volume(-kFloatScaleFactor) {} - -void BootParameters::SavedBootParameters::RegisterJSONConverter( - JSONValueConverter<SavedBootParameters>* converter) { - converter->RegisterIntField("brightness", &SavedBootParameters::brightness); - converter->RegisterIntField("volume", &SavedBootParameters::volume); - converter->RegisterRepeatedString("param_names", - &SavedBootParameters::param_names); - converter->RegisterRepeatedString("param_values", - &SavedBootParameters::param_values); +// Renames the 'next' boot file to the 'last' file and reads its contents. +bool BootParameters::swapAndLoadBootConfigContents(const char *lastBootFile, + const char *nextBootFile, + std::string *contents) { + if (!ReadFileToString(nextBootFile, contents)) { + RemoveFileIfExists(lastBootFile); + return false; + } + + errno = 0; + if (rename(nextBootFile, lastBootFile) && errno != ENOENT) + ALOGE("Unable to swap boot files: %s", strerror(errno)); + + return true; } BootParameters::BootParameters() { - swapBootConfigs(); loadParameters(); } +// Saves the boot parameters state to disk so the framework can read it. +void BootParameters::storeParameters() { + errno = 0; + if (!WriteStringToFile(mProto.SerializeAsString(), kLastBootFile)) { + ALOGE("Failed to write boot parameters to %s: %s", kLastBootFile, strerror(errno)); + } + + // WriteStringToFile sets the file permissions to 0666, but these are not + // honored by the system. + errno = 0; + if (chmod(kLastBootFile, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) { + ALOGE("Failed to set permissions for %s: %s", kLastBootFile, strerror(errno)); + } +} + +// Load the boot parameters from disk, try the old location and format if the +// file does not exist. Note: +// - Parse errors result in defaults being used (a normal boot). +// - Legacy boot parameters default to a silent boot. void BootParameters::loadParameters() { + // Precedence is given to the new file format (.proto). std::string contents; - if (!ReadFileToString(kLastBootFile, &contents)) { - if (errno != ENOENT) - ALOGE("Unable to read from %s: %s", kLastBootFile, strerror(errno)); + if (swapAndLoadBootConfigContents(kLastBootFile, kNextBootFile, &contents)) { + parseBootParameters(contents); + } else if (swapAndLoadBootConfigContents(kLegacyLastBootFile, kLegacyNextBootFile, &contents)) { + parseLegacyBootParameters(contents); + storeParameters(); + removeLegacyFiles(); + } + + createNextBootFile(); +} +void BootParameters::parseBootParameters(const std::string &contents) { + if (!mProto.ParseFromString(contents)) { + ALOGW("Failed to parse parameters from %s", kLastBootFile); return; } - std::unique_ptr<Value> json = JSONReader::Read(contents); - if (json.get() == nullptr) { + loadStateFromProto(); +} + +// Parses the JSON in the proto. +void BootParameters::parseLegacyBootParameters(const std::string &contents) { + Value json; + if (!Reader().parse(contents, json)) { + ALOGW("Failed to parse parameters from %s", kLegacyLastBootFile); return; } - JSONValueConverter<SavedBootParameters> converter; - if (converter.Convert(*(json.get()), &mRawParameters)) { - mBrightness = mRawParameters.brightness / kFloatScaleFactor; - mVolume = mRawParameters.volume / kFloatScaleFactor; - - if (mRawParameters.param_names.size() == mRawParameters.param_values.size()) { - for (size_t i = 0; i < mRawParameters.param_names.size(); i++) { - mParameters.push_back({ - .key = mRawParameters.param_names[i]->c_str(), - .value = mRawParameters.param_values[i]->c_str() - }); + int volume = 0; + bool bootAnimationDisabled = true; + + Value &jsonValue = json[kKeyLegacyVolume]; + if (jsonValue.isIntegral()) { + volume = jsonValue.asInt(); + } + + jsonValue = json[kKeyLegacyAnimationsDisabled]; + if (jsonValue.isIntegral()) { + bootAnimationDisabled = jsonValue.asInt() == 1; + } + + // Assume a silent boot unless all of the following are true - + // 1. The volume is neither 0 nor -1000 (the legacy default value). + // 2. The boot animations are explicitly enabled. + // Note: brightness was never used. + mProto.set_silent_boot((volume == 0) || (volume == -1000) || bootAnimationDisabled); + + Value &keys = json[kKeyLegacyParamNames]; + Value &values = json[kKeyLegacyParamValues]; + if (keys.isArray() && values.isArray() && (keys.size() == values.size())) { + for (ArrayIndex i = 0; i < keys.size(); ++i) { + auto &key = keys[i]; + auto &value = values[i]; + if (key.isString() && value.isString()) { + auto userParameter = mProto.add_user_parameter(); + userParameter->set_key(key.asString()); + userParameter->set_value(value.asString()); } - } else { - ALOGW("Parameter names and values size mismatch"); } } + + loadStateFromProto(); +} + +void BootParameters::loadStateFromProto() { + // A missing key returns a safe, default value. + // Ignore invalid or missing parameters. + mIsSilentBoot = mProto.silent_boot(); + + for (const auto ¶m : mProto.user_parameter()) { + mParameters.push_back({.key = param.key().c_str(), .value = param.value().c_str()}); + } } } // namespace android diff --git a/cmds/bootanimation/iot/BootParameters.h b/cmds/bootanimation/iot/BootParameters.h index c10bd44bc2ca..cbd1ca61cfc3 100644 --- a/cmds/bootanimation/iot/BootParameters.h +++ b/cmds/bootanimation/iot/BootParameters.h @@ -18,10 +18,11 @@ #define _BOOTANIMATION_BOOT_PARAMETERS_H_ #include <list> +#include <string> #include <vector> -#include <base/json/json_value_converter.h> #include <boot_action/boot_action.h> // libandroidthings native API. +#include <boot_parameters.pb.h> namespace android { @@ -32,39 +33,39 @@ public: // to clear the parameters for next boot. BootParameters(); - // Returns true if volume/brightness were explicitly set on reboot. - bool hasVolume() const { return mVolume >= 0; } - bool hasBrightness() const { return mBrightness >= 0; } - - // Returns volume/brightness in [0,1], or -1 if unset. - float getVolume() const { return mVolume; } - float getBrightness() const { return mBrightness; } + // Returns whether or not this is a silent boot. + bool isSilentBoot() const { return mIsSilentBoot; } // Returns the additional boot parameters that were set on reboot. const std::vector<ABootActionParameter>& getParameters() const { return mParameters; } -private: - // Raw boot saved_parameters loaded from .json. - struct SavedBootParameters { - int brightness; - int volume; - std::vector<std::unique_ptr<std::string>> param_names; - std::vector<std::unique_ptr<std::string>> param_values; + // Exposed for testing. Sets the parameters to the serialized proto. + void parseBootParameters(const std::string &contents); + + // For devices that OTA from N to O. + // Exposed for testing. Sets the parameters to the raw JSON. + void parseLegacyBootParameters(const std::string &contents); - SavedBootParameters(); - static void RegisterJSONConverter( - ::base::JSONValueConverter<SavedBootParameters>* converter); - }; + // Exposed for testing. Loads the contents from |nextBootFile| and replaces + // |lastBootFile| with |nextBootFile|. + static bool swapAndLoadBootConfigContents(const char *lastBootFile, const char *nextBootFile, + std::string *contents); + private: void loadParameters(); - float mVolume = -1.f; - float mBrightness = -1.f; + // Replaces the legacy JSON blob with the updated version, allowing the + // framework to read it. + void storeParameters(); + + void loadStateFromProto(); + + bool mIsSilentBoot = false; + std::vector<ABootActionParameter> mParameters; - // ABootActionParameter is just a raw pointer so we need to keep the - // original strings around to avoid losing them. - SavedBootParameters mRawParameters; + // Store the proto because mParameters makes a shallow copy. + android::things::proto::BootParameters mProto; }; } // namespace android diff --git a/cmds/bootanimation/iot/BootParameters_test.cpp b/cmds/bootanimation/iot/BootParameters_test.cpp new file mode 100644 index 000000000000..d55bce6eecc3 --- /dev/null +++ b/cmds/bootanimation/iot/BootParameters_test.cpp @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "BootParameters.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <android-base/file.h> +#include <android-base/test_utils.h> +#include <boot_parameters.pb.h> +#include <gtest/gtest.h> + +namespace android { + +namespace { + +TEST(BootParametersTest, TestNoBootParametersIsNotSilent) { + android::things::proto::BootParameters proto; + + BootParameters bootParameters = BootParameters(); + bootParameters.parseBootParameters(proto.SerializeAsString()); + + ASSERT_FALSE(bootParameters.isSilentBoot()); + ASSERT_EQ(0u, bootParameters.getParameters().size()); +} + +TEST(BootParametersTest, TestParseIsSilent) { + android::things::proto::BootParameters proto; + proto.set_silent_boot(true); + + BootParameters bootParameters = BootParameters(); + bootParameters.parseBootParameters(proto.SerializeAsString()); + + ASSERT_TRUE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseIsNotSilent) { + android::things::proto::BootParameters proto; + proto.set_silent_boot(false); + + BootParameters bootParameters = BootParameters(); + bootParameters.parseBootParameters(proto.SerializeAsString()); + + ASSERT_FALSE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseBootParameters) { + android::things::proto::BootParameters proto; + proto.set_silent_boot(false); + + auto userParameter = proto.add_user_parameter(); + userParameter->set_key("key1"); + userParameter->set_value("value1"); + + userParameter = proto.add_user_parameter(); + userParameter->set_key("key2"); + userParameter->set_value("value2"); + + BootParameters bootParameters = BootParameters(); + bootParameters.parseBootParameters(proto.SerializeAsString()); + + auto ¶meters = bootParameters.getParameters(); + ASSERT_EQ(2u, parameters.size()); + ASSERT_STREQ(parameters[0].key, "key1"); + ASSERT_STREQ(parameters[0].value, "value1"); + ASSERT_STREQ(parameters[1].key, "key2"); + ASSERT_STREQ(parameters[1].value, "value2"); +} + +TEST(BootParametersTest, TestParseLegacyDisableBootAnimationIsSilent) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":100, + "boot_animation_disabled":1, + "param_names":[], + "param_values":[] + } + )"); + + ASSERT_TRUE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseLegacyZeroVolumeIsSilent) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":0, + "boot_animation_disabled":0, + "param_names":[], + "param_values":[] + } + )"); + + ASSERT_TRUE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseLegacyDefaultVolumeIsSilent) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":-1000, + "boot_animation_disabled":0, + "param_names":[], + "param_values":[] + } + )"); + + ASSERT_TRUE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseLegacyNotSilent) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":500, + "boot_animation_disabled":0, + "param_names":[], + "param_values":[] + } + )"); + + ASSERT_FALSE(bootParameters.isSilentBoot()); +} + +TEST(BootParametersTest, TestParseLegacyParameters) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":100, + "boot_animation_disabled":1, + "param_names":["key1", "key2"], + "param_values":["value1", "value2"] + } + )"); + + auto parameters = bootParameters.getParameters(); + ASSERT_EQ(2u, parameters.size()); + ASSERT_STREQ(parameters[0].key, "key1"); + ASSERT_STREQ(parameters[0].value, "value1"); + ASSERT_STREQ(parameters[1].key, "key2"); + ASSERT_STREQ(parameters[1].value, "value2"); +} + +TEST(BootParametersTest, TestParseLegacyZeroParameters) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":200, + "volume":100, + "boot_animation_disabled":1, + "param_names":[], + "param_values":[] + } + )"); + + ASSERT_EQ(0u, bootParameters.getParameters().size()); +} + +TEST(BootParametersTest, TestMalformedLegacyParametersAreSkipped) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":500, + "volume":500, + "boot_animation_disabled":0, + "param_names":["key1", "key2"], + "param_values":[1, "value2"] + } + )"); + + auto parameters = bootParameters.getParameters(); + ASSERT_EQ(1u, parameters.size()); + ASSERT_STREQ(parameters[0].key, "key2"); + ASSERT_STREQ(parameters[0].value, "value2"); +} + +TEST(BootParametersTest, TestLegacyUnequalParameterSizesAreSkipped) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":500, + "volume":500, + "boot_animation_disabled":0, + "param_names":["key1", "key2"], + "param_values":["value1"] + } + )"); + + ASSERT_EQ(0u, bootParameters.getParameters().size()); +} + +TEST(BootParametersTest, TestMissingLegacyBootParametersIsSilent) { + BootParameters bootParameters = BootParameters(); + bootParameters.parseLegacyBootParameters(R"( + { + "brightness":500 + } + )"); + + EXPECT_TRUE(bootParameters.isSilentBoot()); + ASSERT_EQ(0u, bootParameters.getParameters().size()); +} + +TEST(BootParametersTest, TestLastFileIsRemovedOnError) { + TemporaryFile lastFile; + TemporaryDir tempDir; + std::string nonExistentFilePath(std::string(tempDir.path) + "/nonexistent"); + std::string contents; + + BootParameters::swapAndLoadBootConfigContents(lastFile.path, nonExistentFilePath.c_str(), + &contents); + + struct stat buf; + ASSERT_EQ(-1, lstat(lastFile.path, &buf)); + ASSERT_TRUE(contents.empty()); +} + +TEST(BootParametersTest, TestNextFileIsRemovedLastFileExistsOnSuccess) { + TemporaryFile lastFile; + TemporaryFile nextFile; + + base::WriteStringToFile("foo", nextFile.path); + + std::string contents; + // Expected side effects: + // - |next_file| is moved to |last_file| + // - |contents| is the contents of |next_file| before being moved. + BootParameters::swapAndLoadBootConfigContents(lastFile.path, nextFile.path, &contents); + + struct stat buf; + ASSERT_EQ(0, lstat(lastFile.path, &buf)); + ASSERT_EQ(-1, lstat(nextFile.path, &buf)); + ASSERT_EQ(contents, "foo"); + + contents.clear(); + ASSERT_TRUE(base::ReadFileToString(lastFile.path, &contents)); + ASSERT_EQ(contents, "foo"); +} + +} // namespace + +} // namespace android diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp index 00cef430135e..2a3d3766ab38 100644 --- a/cmds/bootanimation/iot/iotbootanimation_main.cpp +++ b/cmds/bootanimation/iot/iotbootanimation_main.cpp @@ -59,7 +59,7 @@ public: } mBootAction = new BootAction(); - if (!mBootAction->init(library_path, mBootParameters->getParameters())) { + if (!mBootAction->init(library_path, mBootParameters)) { mBootAction = NULL; } }; @@ -116,8 +116,16 @@ int main() { sp<ProcessState> proc(ProcessState::self()); ProcessState::self()->startThreadPool(); - sp<BootAnimation> boot = new BootAnimation( - new BootActionAnimationCallbacks(std::move(bootParameters))); + bool isSilentBoot = bootParameters->isSilentBoot(); + sp<BootActionAnimationCallbacks> callbacks = + new BootActionAnimationCallbacks(std::move(bootParameters)); + + // On silent boot, animations aren't displayed. + if (isSilentBoot) { + callbacks->init({}); + } else { + sp<BootAnimation> boot = new BootAnimation(callbacks); + } IPCThreadState::self()->joinThreadPool(); return 0; |