Allow IO During boot process, BootActions.

NOTE: this is only compiled into products with PRODUCT_IOT=true.

Introduce BootActions that a developer can provide to manipulate IO
before the android framework comes up on boot.

We will look for a configuration file at /oem/app/etc/boot_action.conf and
expect it to tell us the name of a shared library. We will then fetch
this library from /oem/app/lib/${arch}/ and load it. We expect it to export
boot_action_init(), boot_action_shutdown(), and optionally
boot_action_start_part(int partNumber, int playNumber).

We will then call boot_action_init() during boot after PeripheralManager
is up and call boot_action_shutdown() when the android framework is up
and we are going to start loading APKs.

We will also call boot_action_start_part(*) when each part of the boot
animation is started, use this if you want to synchronize the boot
action and the boot animation.

Boot actions run in a restricted environment and in general can only
make calls to PeripheralManager.

Bug: 37992717
Test: Pushed to local imx7d to test boot actions, pushed to bullhead test that animation+sound still works.
Change-Id: I9e53a17567f8028ea84486d637e1d231ee1125e1
This commit is contained in:
Ed Coyne
2017-06-08 12:26:48 -07:00
parent 2cb3f59668
commit 7464ac9bd7
9 changed files with 516 additions and 97 deletions

View File

@@ -8,10 +8,6 @@ bootanimation_CommonCFlags += -Wall -Werror -Wunused -Wunreachable-code
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
bootanimation_main.cpp \
audioplay.cpp \
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SHARED_LIBRARIES := \
@@ -24,6 +20,29 @@ LOCAL_SHARED_LIBRARIES := \
liblog \
libutils \
LOCAL_SRC_FILES:= \
BootAnimationUtil.cpp \
ifeq ($(PRODUCT_IOT),true)
LOCAL_SRC_FILES += \
iot/iotbootanimation_main.cpp \
iot/BootAction.cpp
LOCAL_SHARED_LIBRARIES += \
libandroidthings \
libbase \
libbinder
LOCAL_STATIC_LIBRARIES += cpufeatures
else
LOCAL_SRC_FILES += \
bootanimation_main.cpp \
audioplay.cpp \
endif # PRODUCT_IOT
LOCAL_MODULE:= bootanimation
LOCAL_INIT_RC := bootanim.rc
@@ -45,6 +64,8 @@ LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_SRC_FILES:= \
BootAnimation.cpp
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
LOCAL_C_INCLUDES += \
external/tinyalsa/include \
frameworks/wilhelm/include

View File

@@ -96,11 +96,9 @@ static constexpr size_t TEXT_POS_LEN_MAX = 16;
// ---------------------------------------------------------------------------
BootAnimation::BootAnimation(InitCallback initCallback,
PlayPartCallback partCallback)
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(NULL),
mInitCallback(initCallback), mPlayPartCallback(partCallback) {
mTimeFormat12Hour(false), mTimeCheckThread(NULL), mCallbacks(callbacks) {
mSession = new SurfaceComposerClient();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
@@ -357,6 +355,8 @@ bool BootAnimation::android()
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
mCallbacks->init({});
// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
@@ -424,6 +424,7 @@ void BootAnimation::checkExit() {
int exitnow = atoi(value);
if (exitnow) {
requestExit();
mCallbacks->shutdown();
}
}
@@ -777,9 +778,7 @@ bool BootAnimation::preloadZip(Animation& animation)
}
}
if (mInitCallback != nullptr) {
mInitCallback(animation.parts);
}
mCallbacks->init(animation.parts);
zip->endIteration(cookie);
@@ -887,9 +886,7 @@ bool BootAnimation::playAnimation(const Animation& animation)
if(exitPending() && !part.playUntilComplete)
break;
if (mPlayPartCallback != nullptr) {
mPlayPartCallback(i, part, r);
}
mCallbacks->playPart(i, part, r);
glClearColor(
part.backgroundColor[0],

View File

@@ -93,22 +93,27 @@ public:
Font clockFont;
};
// Callback will be called during initialization after we have loaded
// the animation and be provided with all parts in animation.
typedef std::function<void(const Vector<Animation::Part>& parts)> InitCallback;
// All callbacks will be called from this class's internal thread.
class Callbacks : public RefBase {
public:
// Will be called during initialization after we have loaded
// the animation and be provided with all parts in animation.
virtual void init(const Vector<Animation::Part>& /*parts*/) {}
// Callback will be called while animation is playing before each part is
// played. It will be provided with the part and play count for it.
// It will be provided with the partNumber for the part about to be played,
// as well as a reference to the part itself. It will also be provided with
// which play of that part is about to start, some parts are repeated
// multiple times.
typedef std::function<void(int partNumber, const Animation::Part& part, int playNumber)>
PlayPartCallback;
// Will be called while animation is playing before each part is
// played. It will be provided with the part and play count for it.
// It will be provided with the partNumber for the part about to be played,
// as well as a reference to the part itself. It will also be provided with
// which play of that part is about to start, some parts are repeated
// multiple times.
virtual void playPart(int /*partNumber*/, const Animation::Part& /*part*/,
int /*playNumber*/) {}
// Callbacks may be null and will be called from this class's internal
// thread.
BootAnimation(InitCallback initCallback, PlayPartCallback partCallback);
// Will be called when animation is done and thread is shutting down.
virtual void shutdown() {}
};
BootAnimation(sp<Callbacks> callbacks);
sp<SurfaceComposerClient> session() const;
@@ -170,8 +175,7 @@ private:
String8 mZipFileName;
SortedVector<String8> mLoadedFiles;
sp<TimeCheckThread> mTimeCheckThread = nullptr;
InitCallback mInitCallback = nullptr;
PlayPartCallback mPlayPartCallback = nullptr;
sp<Callbacks> mCallbacks;
};
// ---------------------------------------------------------------------------

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2017 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 "BootAnimationUtil.h"
#include <inttypes.h>
#include <binder/IServiceManager.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>
namespace android {
bool bootAnimationDisabled() {
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
if (atoi(value) > 0) {
return false;
}
property_get("ro.boot.quiescent", value, "0");
return atoi(value) > 0;
}
void waitForSurfaceFlinger() {
// TODO: replace this with better waiting logic in future, b/35253872
int64_t waitStartTime = elapsedRealtime();
sp<IServiceManager> sm = defaultServiceManager();
const String16 name("SurfaceFlinger");
const int SERVICE_WAIT_SLEEP_MS = 100;
const int LOG_PER_RETRIES = 10;
int retry = 0;
while (sm->checkService(name) == nullptr) {
retry++;
if ((retry % LOG_PER_RETRIES) == 0) {
ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms",
elapsedRealtime() - waitStartTime);
}
usleep(SERVICE_WAIT_SLEEP_MS * 1000);
};
int64_t totalWaited = elapsedRealtime() - waitStartTime;
if (totalWaited > SERVICE_WAIT_SLEEP_MS) {
ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);
}
}
} // namespace android

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2017 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.
*/
namespace android {
// Returns true if boot animation is disabled.
bool bootAnimationDisabled();
// Waits until the surface flinger is up.
void waitForSurfaceFlinger();
} // namespace android

View File

@@ -30,6 +30,7 @@
#include <android-base/properties.h>
#include "BootAnimation.h"
#include "BootAnimationUtil.h"
#include "audioplay.h"
using namespace android;
@@ -95,6 +96,49 @@ bool playSoundsAllowed() {
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 = &part;
}
}
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
@@ -102,83 +146,19 @@ int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
int noBootAnimation = atoi(value);
if (!noBootAnimation) {
property_get("ro.boot.quiescent", value, "0");
noBootAnimation = atoi(value);
}
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// TODO: replace this with better waiting logic in future, b/35253872
int64_t waitStartTime = elapsedRealtime();
sp<IServiceManager> sm = defaultServiceManager();
const String16 name("SurfaceFlinger");
const int SERVICE_WAIT_SLEEP_MS = 100;
const int LOG_PER_RETRIES = 10;
int retry = 0;
while (sm->checkService(name) == nullptr) {
retry++;
if ((retry % LOG_PER_RETRIES) == 0) {
ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms",
elapsedRealtime() - waitStartTime);
}
usleep(SERVICE_WAIT_SLEEP_MS * 1000);
};
int64_t totalWaited = elapsedRealtime() - waitStartTime;
if (totalWaited > SERVICE_WAIT_SLEEP_MS) {
ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);
}
// TODO: Move audio code to a new class that just exports the callbacks.
sp<InitAudioThread> initAudioThread = nullptr;
auto initCallback = [&](const Vector<Animation::Part>& parts) {
const Animation::Part* partWithAudio = nullptr;
for (const Animation::Part& part : parts) {
if (part.audioData != nullptr) {
partWithAudio = &part;
}
}
if (partWithAudio == nullptr) {
return;
}
ALOGD("found audio.wav, creating playback engine");
initAudioThread = new InitAudioThread(partWithAudio->audioData,
partWithAudio->audioLength);
initAudioThread->run("BootAnimation::InitAudioThread", PRIORITY_NORMAL);
};
auto partCallback = [&](int partNumber, const Animation::Part& part,
int playNumber) {
// 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);
}
};
waitForSurfaceFlinger();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(initCallback, partCallback);
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
IPCThreadState::self()->joinThreadPool();
// we've finally played everything we're going to play
audioplay::setPlaying(false);
audioplay::destroy();
}
return 0;
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright (C) 2017 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 "BootAction.h"
#define LOG_TAG "BootAction"
#include <android-base/strings.h>
#include <cpu-features.h>
#include <dlfcn.h>
#include <pio/peripheral_manager_client.h>
#include <utils/Log.h>
using android::base::Split;
using android::base::Join;
using android::base::StartsWith;
using android::base::EndsWith;
namespace android {
BootAction::~BootAction() {
if (mLibHandle != nullptr) {
dlclose(mLibHandle);
}
}
bool BootAction::init(const std::string& libraryPath, const std::string& config) {
APeripheralManagerClient* client = nullptr;
ALOGD("Connecting to peripheralmanager");
// Wait for peripheral manager to come up.
while (client == nullptr) {
client = APeripheralManagerClient_new();
if (client == nullptr) {
ALOGV("peripheralmanager is not up, sleeping before we check again.");
usleep(250000);
}
}
ALOGD("Peripheralmanager is up.");
APeripheralManagerClient_delete(client);
std::string path_to_lib = libraryPath;
if (!parseConfig(config, &path_to_lib)) {
return false;
}
ALOGI("Loading boot action %s", path_to_lib.c_str());
mLibHandle = dlopen(path_to_lib.c_str(), RTLD_NOW);
if (mLibHandle == nullptr) {
ALOGE("Unable to load library at %s :: %s",
path_to_lib.c_str(), dlerror());
return false;
}
void* loaded = nullptr;
if (!loadSymbol("boot_action_init", &loaded) || loaded == nullptr) {
return false;
}
mLibInit = reinterpret_cast<libInit>(loaded);
loaded = nullptr;
if (!loadSymbol("boot_action_shutdown", &loaded) || loaded == nullptr) {
return false;
}
mLibShutdown = reinterpret_cast<libShutdown>(loaded);
// StartPart is considered optional, if it isn't exported by the library
// we will still call init and shutdown.
loaded = nullptr;
if (!loadSymbol("boot_action_start_part", &loaded) || loaded == nullptr) {
ALOGI("No boot_action_start_part found, action will not be told when "
"Animation parts change.");
} else {
mLibStartPart = reinterpret_cast<libStartPart>(loaded);
}
ALOGD("Entering boot_action_init");
bool result = mLibInit();
ALOGD("Returned from boot_action_init");
return result;
}
void BootAction::startPart(int partNumber, int playNumber) {
if (mLibStartPart == nullptr) return;
ALOGD("Entering boot_action_start_part");
mLibStartPart(partNumber, playNumber);
ALOGD("Returned from boot_action_start_part");
}
void BootAction::shutdown() {
ALOGD("Entering boot_action_shutdown");
mLibShutdown();
ALOGD("Returned from boot_action_shutdown");
}
bool BootAction::loadSymbol(const char* symbol, void** loaded) {
*loaded = dlsym(mLibHandle, symbol);
if (loaded == nullptr) {
ALOGE("Unable to load symbol : %s :: %s", symbol, dlerror());
return false;
}
return true;
}
bool BootAction::parseConfig(const std::string& config, std::string* path) {
auto lines = Split(config, "\n");
if (lines.size() < 1) {
ALOGE("Config format invalid, expected one line, found %d",
lines.size());
return false;
}
size_t lineNumber = 0;
auto& line1 = lines.at(lineNumber);
while (StartsWith(line1, "#")) {
if (lines.size() < ++lineNumber) {
ALOGE("Config file contains no non-comment lines.");
return false;
}
line1 = lines.at(lineNumber);
}
const std::string libraryNameToken("LIBRARY_NAME=");
if (!StartsWith(line1, libraryNameToken.c_str())) {
ALOGE("Invalid config format, expected second line to start with %s "
"Instead found: %s", libraryNameToken.c_str(), line1.c_str());
return false;
}
std::string libraryName = line1.substr(libraryNameToken.length());
*path += "/";
*path += architectureDirectory();
*path += "/";
*path += libraryName;
return true;
}
const char* BootAction::architectureDirectory() {
switch(android_getCpuFamily()) {
case ANDROID_CPU_FAMILY_ARM:
return "arm";
case ANDROID_CPU_FAMILY_X86:
return "x86";
case ANDROID_CPU_FAMILY_MIPS:
return "mips";
case ANDROID_CPU_FAMILY_ARM64:
return "arm64";
case ANDROID_CPU_FAMILY_X86_64:
return "x86_64";
case ANDROID_CPU_FAMILY_MIPS64:
return "mips64";
default:
ALOGE("Unsupported cpu family: %d", android_getCpuFamily());
return "";
}
}
} // namespace android

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2017 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_BOOTACTION_H
#define _BOOTANIMATION_BOOTACTION_H
#include <string>
#include <utils/RefBase.h>
namespace android {
class BootAction : public RefBase {
public:
~BootAction();
// Parse the contents of the config file. We expect one line:
// LIBRARY_NAME=
//
// LIBRARY_NAME is the name of the shared library that contains the boot action.
bool init(const std::string& libraryPath, const std::string& config);
// The animation is going to start playing partNumber for the playCount'th
// time, update the action as needed.
// This is run in the same thread as the boot animation,
// you must not block here.
void startPart(int partNumber, int playCount);
// Shutdown the boot action, this will be called shortly before the
// process is shut down to allow time for cleanup.
void shutdown();
private:
typedef bool (*libInit)();
typedef void (*libStartPart)(int partNumber, int playNumber);
typedef void (*libShutdown)();
bool parseConfig(const std::string& config, std::string* path);
bool loadSymbol(const char* symbol, void** loaded);
const char* architectureDirectory();
void* mLibHandle = nullptr;
libInit mLibInit = nullptr;
libStartPart mLibStartPart = nullptr;
libShutdown mLibShutdown = nullptr;
};
} // namespace android
#endif // _BOOTANIMATION_BOOTACTION_H

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2017 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_TAG "IotBootAnimation"
#include <android-base/file.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <cutils/properties.h>
#include <sys/resource.h>
#include <utils/Log.h>
#include <utils/threads.h>
#include <BootAnimation.h>
#include "BootAction.h"
#include "BootAnimationUtil.h"
using namespace android;
using android::base::ReadFileToString;
// Create a typedef for readability.
typedef android::BootAnimation::Animation Animation;
namespace {
class BootActionAnimationCallbacks : public android::BootAnimation::Callbacks {public:
void init(const Vector<Animation::Part>&) override {
// Create and initialize BootActions if we have a boot_action.conf.
std::string bootActionConf;
if (ReadFileToString("/oem/app/etc/boot_action.conf", &bootActionConf)) {
mBootAction = new BootAction();
if (!mBootAction->init("/oem/app/lib", bootActionConf)) {
mBootAction = NULL;
}
} else {
ALOGI("No boot actions specified");
}
};
void playPart(int partNumber, const Animation::Part&, int playNumber) override {
if (mBootAction != nullptr) {
mBootAction->startPart(partNumber, playNumber);
}
};
void shutdown() override {
if (mBootAction != nullptr) {
mBootAction->shutdown();
// Give it two seconds to shut down.
sleep(2);
mBootAction = nullptr;
}
};
private:
sp<BootAction> mBootAction = nullptr;
};
} // namespace
int main() {
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
if (bootAnimationDisabled()) {
ALOGI("boot animation disabled");
return 0;
}
waitForSurfaceFlinger();
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
sp<BootAnimation> boot = new BootAnimation(new BootActionAnimationCallbacks());
IPCThreadState::self()->joinThreadPool();
return 0;
}