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
diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk
index 7ab402a..73ec63f 100644
--- a/cmds/bootanimation/Android.mk
+++ b/cmds/bootanimation/Android.mk
@@ -8,10 +8,6 @@
 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 @@
     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_SRC_FILES:= \
     BootAnimation.cpp
 
+LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
+
 LOCAL_C_INCLUDES += \
     external/tinyalsa/include \
     frameworks/wilhelm/include
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 6b2de4b..6526123 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -96,11 +96,9 @@
 
 // ---------------------------------------------------------------------------
 
-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 @@
     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 @@
     int exitnow = atoi(value);
     if (exitnow) {
         requestExit();
+        mCallbacks->shutdown();
     }
 }
 
@@ -777,9 +778,7 @@
         }
     }
 
-    if (mInitCallback != nullptr) {
-        mInitCallback(animation.parts);
-    }
+    mCallbacks->init(animation.parts);
 
     zip->endIteration(cookie);
 
@@ -887,9 +886,7 @@
             if(exitPending() && !part.playUntilComplete)
                 break;
 
-            if (mPlayPartCallback != nullptr) {
-                mPlayPartCallback(i, part, r);
-            }
+            mCallbacks->playPart(i, part, r);
 
             glClearColor(
                     part.backgroundColor[0],
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 3ebe7d6..56e131523 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -93,22 +93,27 @@
         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 @@
     String8     mZipFileName;
     SortedVector<String8> mLoadedFiles;
     sp<TimeCheckThread> mTimeCheckThread = nullptr;
-    InitCallback mInitCallback = nullptr;
-    PlayPartCallback mPlayPartCallback = nullptr;
+    sp<Callbacks> mCallbacks;
 };
 
 // ---------------------------------------------------------------------------
diff --git a/cmds/bootanimation/BootAnimationUtil.cpp b/cmds/bootanimation/BootAnimationUtil.cpp
new file mode 100644
index 0000000..377d6ce
--- /dev/null
+++ b/cmds/bootanimation/BootAnimationUtil.cpp
@@ -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
diff --git a/cmds/bootanimation/BootAnimationUtil.h b/cmds/bootanimation/BootAnimationUtil.h
new file mode 100644
index 0000000..60987cd1
--- /dev/null
+++ b/cmds/bootanimation/BootAnimationUtil.h
@@ -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
diff --git a/cmds/bootanimation/bootanimation_main.cpp b/cmds/bootanimation/bootanimation_main.cpp
index c11d905..daac588 100644
--- a/cmds/bootanimation/bootanimation_main.cpp
+++ b/cmds/bootanimation/bootanimation_main.cpp
@@ -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 @@
     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 @@
 {
     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;
 }
diff --git a/cmds/bootanimation/iot/BootAction.cpp b/cmds/bootanimation/iot/BootAction.cpp
new file mode 100644
index 0000000..8fda87e
--- /dev/null
+++ b/cmds/bootanimation/iot/BootAction.cpp
@@ -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
diff --git a/cmds/bootanimation/iot/BootAction.h b/cmds/bootanimation/iot/BootAction.h
new file mode 100644
index 0000000..31d0d1f
--- /dev/null
+++ b/cmds/bootanimation/iot/BootAction.h
@@ -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
diff --git a/cmds/bootanimation/iot/iotbootanimation_main.cpp b/cmds/bootanimation/iot/iotbootanimation_main.cpp
new file mode 100644
index 0000000..d1ae786
--- /dev/null
+++ b/cmds/bootanimation/iot/iotbootanimation_main.cpp
@@ -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;
+}