/*
* Copyright (C) 2020 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 "MultiDisplay.h"

#include <stddef.h>                                      // for size_t
#include <algorithm>                                     // for max
#include <cstdint>                                       // for uint32_t
#include <ostream>                                       // for operator<<
#include <set>                                           // for set
#include <string>                                        // for string, stoi
#include <unordered_map>                                 // for unordered_map
#include <utility>                                       // for pair, make_pair
#include <vector>                                        // for vector

#include "android/base/LayoutResolver.h"                 // for resolveLayout
#include "android/base/Log.h"                            // for LogStreamVoi...
#include "android/base/files/Stream.h"                   // for Stream
#include "android/base/files/StreamSerializing.h"        // for loadCollection
#include "android/cmdline-option.h"                      // for android_cmdL...
#include "android/emulation/MultiDisplayPipe.h"          // for MultiDisplay...
#include "android/emulation/control/adb/AdbInterface.h"  // for AdbInterface
#include "android/emulator-window.h"                     // for emulator_win...
#include "android/featurecontrol/FeatureControl.h"       // for isEnabled
#include "android/featurecontrol/Features.h"             // for MultiDisplay
#include "android/globals.h"                             // for android_hw
#include "android/hw-sensors.h"                          // for android_fold...
#include "android/recording/screen-recorder.h"           // for RecorderStates
#include "android/skin/file.h"                           // for SkinLayout
#include "android/skin/rect.h"                           // for SKIN_ROTATION_0

using android::base::AutoLock;

namespace android {

static MultiDisplay* sMultiDisplay = nullptr;

MultiDisplay::MultiDisplay(const QAndroidEmulatorWindowAgent* const windowAgent,
                           const QAndroidRecordScreenAgent* const recordAgent,
                           bool isGuestMode)
    : mWindowAgent(windowAgent),
      mRecordAgent(recordAgent),
      mGuestMode(isGuestMode) { }

//static
MultiDisplay* MultiDisplay::getInstance() {
    return sMultiDisplay;
}

int MultiDisplay::setMultiDisplay(uint32_t id,
                                  int32_t x,
                                  int32_t y,
                                  uint32_t w,
                                  uint32_t h,
                                  uint32_t dpi,
                                  uint32_t flag,
                                  bool add) {
    int ret = 0;
    SkinRotation rotation = SKIN_ROTATION_0;
    LOG(VERBOSE) << "setMultiDisplay id " << id << " "
                 << x << " " << y << " " << w << " " << h << " "
                << dpi << " " << flag << " " << (add? "add":"del");
    if (!featurecontrol::isEnabled(android::featurecontrol::MultiDisplay)) {
        return -1;
    }
    if (android_foldable_any_folded_area_configured()) {
        return -1;
    }
    // TODO (wdu@) Remove this when multidisplay is supported by embedded
    // emulator.
    if (android_cmdLineOptions->qt_hide_window) {
        return -1;
    }
    if (mGuestMode) {
        return -1;
    }
    if (add && !multiDisplayParamValidate(id, w, h, dpi, flag)) {
        return -1;
    }

    // fetch rotation from EmulatorWindow
    // TODO: link to libui source code???
    EmulatorWindow* window = emulator_window_get();
    if (window) {
        SkinLayout* layout = emulator_window_get_layout(window);
        if (layout) {
            rotation = layout->orientation;
        }
    }
    if (rotation != SKIN_ROTATION_0) {
        mWindowAgent->showMessage("Please apply multiple displays without rotation",
                                  WINDOW_MESSAGE_ERROR, 1000);
        return -1;
    }

    if (add) {
        ret = createDisplay(&id);
        if (ret != 0) {
            return ret;
        }
        ret = setDisplayPose(id, x, y, w, h, dpi);
        if (ret != 0) {
            return ret;
        }
    } else {
        ret = destroyDisplay(id);
        if (ret != 0) {
            return ret;
        }
    }

    // Service in guest has already started through QemuMiscPipe when
    // bootCompleted. But this service may be killed, e.g., Android low
    // memory. Send broadcast again to guarantee servce running.
    // P.S. guest Service has check to avoid duplication.
    auto adbInterface = emulation::AdbInterface::getGlobal();
    if (!adbInterface) {
        LOG(ERROR) << "Adb interface unavailable";
        return -1;
    }
    adbInterface->enqueueCommand(
        {"shell", "am", "broadcast", "-a",
         "com.android.emulator.multidisplay.START", "-n",
         "com.android.emulator.multidisplay/"
         ".MultiDisplayServiceReceiver"});

    MultiDisplayPipe* pipe = MultiDisplayPipe::getInstance();
    if (pipe) {
        std::vector<uint8_t> data;
        pipe->fillData(data, id, w, h, dpi, flag, add);
        LOG(VERBOSE) << "MultiDisplayPipe send " << (add ? "add":"del") << " id " << id
                     << " width " << w << " height " << h << " dpi " << dpi
                     << " flag " << flag;
        pipe->send(std::move(data));
    }
    return 0;
}

bool MultiDisplay::getMultiDisplay(uint32_t id,
                                  int32_t* x,
                                  int32_t* y,
                                  uint32_t* w,
                                  uint32_t* h,
                                  uint32_t* dpi,
                                  uint32_t* flag,
                                  uint32_t* cb,
                                  bool* enabled) {
    AutoLock lock(mLock);
    if (mMultiDisplay.find(id) == mMultiDisplay.end()) {
        if (enabled) {
            *enabled = false;
        }
        return false;
    }
    if (x) {
        *x = mMultiDisplay[id].pos_x;
    }
    if (y) {
        *y = mMultiDisplay[id].pos_y;
    }
    if (w) {
        *w = mMultiDisplay[id].width;
    }
    if (h) {
        *h = mMultiDisplay[id].height;
    }
    if (dpi) {
        *dpi = mMultiDisplay[id].dpi;
    }
    if (flag) {
        *flag = mMultiDisplay[id].flag;
    }
    if (enabled) {
        *enabled = mMultiDisplay[id].enabled;
    }
    LOG(VERBOSE) << "getMultiDisplay " << id <<  "x " << mMultiDisplay[id].pos_x
                 << " y " << mMultiDisplay[id].pos_y
                 << " w " << mMultiDisplay[id].width
                 << " h " << mMultiDisplay[id].height
                 << " dpi " << mMultiDisplay[id].dpi
                 << " flag " << mMultiDisplay[id].flag
                 << " enable " << mMultiDisplay[id].enabled;
    return mMultiDisplay[id].enabled;
}

bool MultiDisplay::getNextMultiDisplay(int32_t start_id,
                                       uint32_t* id,
                                       int32_t* x,
                                       int32_t* y,
                                       uint32_t* w,
                                       uint32_t* h,
                                       uint32_t* dpi,
                                       uint32_t* flag,
                                       uint32_t* cb) {
    uint32_t key;
    std::map<uint32_t, MultiDisplayInfo>::iterator i;

    AutoLock lock(mLock);
    if (start_id < 0) {
        key = 0;
    } else {
        key = start_id + 1;
    }
    i = mMultiDisplay.lower_bound(key);
    if (i == mMultiDisplay.end()) {
        return false;
    } else {
        if (id) {
            *id = i->first;
        }
        if (x) {
            *x = i->second.pos_x;
        }
        if (y) {
            *y = i->second.pos_y;
        }
        if (w) {
            *w = i->second.width;
        }
        if (h) {
            *h = i->second.height;
        }
        if (dpi) {
            *dpi = i->second.dpi;
        }
        if (flag) {
            *flag = i->second.flag;
        }
        if (cb) {
            *cb = i->second.cb;
        }
        return true;
    }
}

bool MultiDisplay::translateCoordination(uint32_t* x, uint32_t* y, uint32_t* displayId) {
    if (mGuestMode) {
        *displayId = 0;
        return true;
    }
    AutoLock lock(mLock);
    uint32_t totalH, pos_x, pos_y, w, h;
    getCombinedDisplaySizeLocked(nullptr, &totalH);
    for (const auto iter : mMultiDisplay) {
        if (iter.first != 0 && iter.second.cb == 0) {
            continue;
        }
        // QT window uses the top left corner as the origin.
        // So we need to transform the (x, y) coordinates from
        // bottom left corner to top left corner.
        pos_x = iter.second.pos_x;
        pos_y = totalH - iter.second.height - iter.second.pos_y;
        w = iter.second.width;
        h = iter.second.height;
        if ((*x - pos_x) < w && (*y - pos_y) < h) {
            *x = *x - pos_x;
            *y = *y - pos_y;
            *displayId = iter.first;
            return true;
        }
    }
    return false;
}

void MultiDisplay::setGpuMode(bool isGuestMode, uint32_t w, uint32_t h) {
    mGuestMode = isGuestMode;
    if (isGuestMode) {
        // Guest mode will not start renderer, which in turn will not set the
        // default display from FrameBuffer. So we set display 0 here.
        AutoLock lock(mLock);
        mMultiDisplay.emplace(0, MultiDisplayInfo(0, 0, w, h, 0, 0, true, 0));
    }
}

int MultiDisplay::createDisplay(uint32_t* displayId) {
    if (mGuestMode) {
        return -1;
    }

    if (displayId == nullptr) {
        LOG(ERROR) << "null displayId pointer";
        return -1;
    }

    AutoLock lock(mLock);

    if (mMultiDisplay.size() > s_maxNumMultiDisplay) {
        LOG(ERROR) << "cannot create more displays, exceeding limits "
            << s_maxNumMultiDisplay;
        return -1;
    }
    if (mMultiDisplay.find(*displayId) != mMultiDisplay.end()) {
        return 0;
    }

    // displays created by internal rcCommands
    if (*displayId == s_invalidIdMultiDisplay) {
        for (int i = s_displayIdInternalBegin; i < s_maxNumMultiDisplay; i++) {
            if (mMultiDisplay.find(i) == mMultiDisplay.end()) {
                *displayId = i;
                break;
            }
        }
    }
    if (*displayId == s_invalidIdMultiDisplay) {
        LOG(ERROR) << "cannot create more internaldisplays, exceeding limits " <<
            s_maxNumMultiDisplay - s_displayIdInternalBegin;
        return -1;
    }

    mMultiDisplay.emplace(*displayId, MultiDisplayInfo());
    LOG(VERBOSE) << "create display " << *displayId;
    return 0;
}

int MultiDisplay::destroyDisplay(uint32_t displayId) {
    uint32_t width, height;
    bool needUIUpdate = false;
    bool restoreSkin = false;

    if (mGuestMode) {
        return -1;
    }
    {
        AutoLock lock(mLock);
        if (mMultiDisplay.find(displayId) == mMultiDisplay.end()) {
            return 0;
        }
        needUIUpdate = ((mMultiDisplay[displayId].cb != 0) ? true : false);
        mMultiDisplay.erase(displayId);
        if (needUIUpdate) {
            recomputeLayoutLocked();
            getCombinedDisplaySizeLocked(&width, &height);
            if (getNumberActiveMultiDisplaysLocked() == 1) {
                // only display 0 remains, restore skin
                restoreSkin = true;
            }
        }
    }

    if (needUIUpdate) {
        // stop recording of this display if it is happening.
        RecorderStates states = mRecordAgent->getRecorderState();
        if (states.displayId == displayId && states.state == RECORDER_RECORDING) {
            mRecordAgent->stopRecording();
        }
        mWindowAgent->setUIDisplayRegion(0, 0, width, height);
        if (restoreSkin) {
            mWindowAgent->restoreSkin();
        }
    }
    LOG(VERBOSE) << "delete display " << displayId;
    return 0;
}

int MultiDisplay::setDisplayPose(uint32_t displayId,
                                int32_t x,
                                int32_t y,
                                uint32_t w,
                                uint32_t h,
                                uint32_t dpi) {
    bool UIUpdate = false;
    bool checkRecording = false;
    uint32_t width, height;
    if (mGuestMode) {
        return -1;
    }
    {
        AutoLock lock(mLock);
        if (mMultiDisplay.find(displayId) == mMultiDisplay.end()) {
            LOG(ERROR) << "cannot find display " << displayId;
            return -1;
        }
        if (mMultiDisplay[displayId].cb != 0 &&
            (mMultiDisplay[displayId].width != w || mMultiDisplay[displayId].height != h)) {
                checkRecording = true;
        }
        mMultiDisplay[displayId].width = w;
        mMultiDisplay[displayId].height = h;
        mMultiDisplay[displayId].dpi = dpi;
        mMultiDisplay[displayId].pos_x = x;
        mMultiDisplay[displayId].pos_y = y;
        if (mMultiDisplay[displayId].cb != 0) {
            if (x == -1 && y == -1) {
                recomputeLayoutLocked();
            }
            getCombinedDisplaySizeLocked(&width, &height);
            UIUpdate = true;
        }
    }
    if (checkRecording) {
        // stop recording of this display if it is happening.
        RecorderStates states = mRecordAgent->getRecorderState();
        if (states.displayId == displayId && states.state == RECORDER_RECORDING) {
            mRecordAgent->stopRecording();
        }
    }
    if (UIUpdate) {
        mWindowAgent->setUIDisplayRegion(0, 0, width, height);
    }
    LOG(VERBOSE) << "setDisplayPose " << displayId << " x " << x
                 << " y " << y << " w " << w << " h " << h
                 << " dpi " << dpi;
    return 0;
}

int MultiDisplay::getDisplayPose(uint32_t displayId,
                                 int32_t* x,
                                 int32_t* y,
                                 uint32_t* w,
                                 uint32_t* h) {
    if (mGuestMode) {
        return -1;
    }
    AutoLock lock(mLock);
    if (mMultiDisplay.find(displayId) == mMultiDisplay.end()) {
        LOG(ERROR) << "cannot find display " << displayId;
        return -1;
    }
    *x = mMultiDisplay[displayId].pos_x;
    *y = mMultiDisplay[displayId].pos_y;
    *w = mMultiDisplay[displayId].width;
    *h = mMultiDisplay[displayId].height;
    return 0;
}

int MultiDisplay::setDisplayColorBuffer(uint32_t displayId, uint32_t colorBuffer) {
    uint32_t width, height;
    bool noSkin = false;
    bool needUpdate = false;
    if (mGuestMode) {
        return -1;
    }
    {
        AutoLock lock(mLock);
        if (mMultiDisplay.find(displayId) == mMultiDisplay.end()) {
            LOG(ERROR) << "cannot find display" << displayId;
            return -1;
        }
        if (mMultiDisplay[displayId].cb == colorBuffer) {
            return 0;
        }
        if (mMultiDisplay[displayId].cb == 0) {
            mMultiDisplay[displayId].cb = colorBuffer;
            // first time cb assigned, update the UI
            needUpdate = true;
            recomputeLayoutLocked();
            getCombinedDisplaySizeLocked(&width, &height);
            if (getNumberActiveMultiDisplaysLocked() == 2) {
                //disable skin when first display set, index 0 is the default one.
                noSkin = true;
            }
        }
        mMultiDisplay[displayId].cb = colorBuffer;
    }
    if (noSkin) {
        mWindowAgent->setNoSkin();
    }
    if (needUpdate) {
        // Explicitly adjust host window size
        mWindowAgent->setUIDisplayRegion(0, 0, width, height);
    }
    LOG(VERBOSE) << "setDisplayColorBuffer " << displayId << " cb " << colorBuffer;
    return 0;
}

int MultiDisplay::getDisplayColorBuffer(uint32_t displayId, uint32_t* colorBuffer) {
    if (mGuestMode) {
        return -1;
    }
    AutoLock lock(mLock);
    if (mMultiDisplay.find(displayId) == mMultiDisplay.end()) {
        return -1;
    }
    *colorBuffer = mMultiDisplay[displayId].cb;
    return 0;
}

int MultiDisplay::getColorBufferDisplay(uint32_t colorBuffer, uint32_t* displayId) {
    if (mGuestMode) {
        return -1;
    }
    AutoLock lock(mLock);
    for (const auto& iter : mMultiDisplay) {
        if (iter.second.cb == colorBuffer) {
            *displayId = iter.first;
            return 0;
        }
    }
    return -1;
}

void MultiDisplay::getCombinedDisplaySize(uint32_t* w, uint32_t* h) {
    AutoLock lock(mLock);
    getCombinedDisplaySizeLocked(w, h);
}

void MultiDisplay::getCombinedDisplaySizeLocked(uint32_t* w, uint32_t* h) {
    uint32_t total_h = 0;
    uint32_t total_w = 0;
    for (const auto& iter : mMultiDisplay) {
        if (iter.first == 0 || iter.second.cb != 0) {
            total_h = std::max(total_h, iter.second.height + iter.second.pos_y);
            total_w = std::max(total_w, iter.second.width + iter.second.pos_x);
        }
    }
    if (h)
        *h = total_h;
    if (w)
        *w = total_w;
}

int MultiDisplay::getNumberActiveMultiDisplaysLocked() {
    int count = 0;
    for (const auto& iter : mMultiDisplay) {
        if (iter.first == 0 || iter.second.cb != 0) {
            count++;
        }
    }
    return count;
}

/*
 * Given that there are at most 11 displays, we can iterate through all possible
 * ways of showing each display in either the first row or the second row. It is
 * also possible to have an empty row. The best combination is to satisfy the
 * following two criteria: 1, The combined rectangle which contains all the
 * displays should have an aspect ratio that is close to the monitor's aspect
 * ratio. 2, The width of the first row should be close to the width of the
 * second row.
 *
 * Important detail of implementations: the x and y offsets saved in
 * mMultiDisplay use the bottom-left corner as origin. This coordinates will
 * be used by glviewport() in Postworker.cpp. However, the x and y offsets saved
 * by invoking setUIMultiDisplay() will be using top-left corner as origin. Thus,
 * input coordinates willl be calculated correctly when mouse events are
 * captured by QT window.
 *
 * TODO: We assume all displays pos_x/pos_y is adjustable here. This may
 * overwrite the specified pos_x/pos_y in setDisplayPos();
 */
void MultiDisplay::recomputeLayoutLocked() {
    uint32_t monitorWidth, monitorHeight;
    double monitorAspectRatio = 1.0;
    if (!mWindowAgent->getMonitorRect(&monitorWidth, &monitorHeight)) {
        LOG(WARNING) << "Fail to get monitor width and height, use default ratio 1.0";
    } else {
        monitorAspectRatio = (double) monitorHeight / (double) monitorWidth;
    }
    std::unordered_map<uint32_t, std::pair<uint32_t, uint32_t>> rectangles;
    for (const auto& iter : mMultiDisplay) {
        if (iter.first == 0 || iter.second.cb != 0) {
            rectangles[iter.first] =
                std::make_pair(iter.second.width, iter.second.height);
        }
    }
    for (const auto& iter :
        android::base::resolveLayout(rectangles, monitorAspectRatio)) {
        mMultiDisplay[iter.first].pos_x = iter.second.first;
        mMultiDisplay[iter.first].pos_y = iter.second.second;
    }
}

bool MultiDisplay::multiDisplayParamValidate(uint32_t id, uint32_t w, uint32_t h,
                                             uint32_t dpi, uint32_t flag) {
    // According the Android 9 CDD,
    // * 120 <= dpi <= 640
    // * 320 * (dpi / 160) <= width
    // * 320 * (dpi / 160) <= height
    // * Screen aspect ratio cannot be longer (or wider) than 21:9 (or 9:21).
    //
    // Also we don't want a screen too big to limit the performance impact.
    // * 4K might be a good upper limit

    if (dpi < 120 || dpi > 640) {
        mWindowAgent->showMessage("dpi should be between 120 and 640",
                                  WINDOW_MESSAGE_ERROR, 1000);
        LOG(ERROR) << "dpi should be between 120 and 640";
        return false;
    }
    if (w < 320 * dpi / 160 || h < 320 * dpi / 160) {
        mWindowAgent->showMessage("width and height should be >= 320dp",
                                  WINDOW_MESSAGE_ERROR, 1000);
        LOG(ERROR) << "width and height should be >= 320dp";
        return false;
    }
    if (!((w <= 4096 && h <= 2160) || (w <= 2160 && h <= 4096))) {
        mWindowAgent->showMessage("resolution should not exceed 4k (4096*2160)",
                                  WINDOW_MESSAGE_ERROR, 1000);
        LOG(ERROR) << "resolution should not exceed 4k (4096*2160)";
        return false;
    }
    if (w * 21 < h * 9 || w * 9 > h * 21) {
        mWindowAgent->showMessage("Aspect ratio cannot be longer (or wider) than 21:9 (or 9:21)",
                                  WINDOW_MESSAGE_ERROR, 1000);
        LOG(ERROR) << "Aspect ratio cannot be longer (or wider) than 21:9 (or 9:21)";
        return false;
    }
    if (id > s_maxNumMultiDisplay) {
        mWindowAgent->showMessage("Display index cannot be more than 3",
                                  WINDOW_MESSAGE_ERROR, 1000);
        LOG(ERROR) << "Display index cannot be more than 3";
        return false;
    }
    return true;
}

std::map<uint32_t, MultiDisplayInfo> MultiDisplay::parseConfig() {
    std::map<uint32_t, MultiDisplayInfo> ret;
    if (!android_cmdLineOptions || !android_cmdLineOptions->multidisplay) {
        return ret;
    }
    std::string s = android_cmdLineOptions->multidisplay;
    std::vector<uint32_t> params;
    size_t last = 0, next = 0;
    while ((next = s.find(",", last)) != std::string::npos) {
        params.push_back(std::stoi(s.substr(last, next - last)));
        last = next + 1;
    }
    params.push_back(std::stoi(s.substr(last)));
    if (params.size() < 5 || params.size() % 5 != 0) {
        LOG(ERROR) << "Not enough parameters for multidisplay command";
        return ret;
    }
    int i = 0;
    for (i = 0; i < params.size(); i+=5) {
        if (params[i] == 0 || params[i] > 3) {
            LOG(ERROR) << "multidisplay index should only be 1, 2, or 3";
            ret.clear();
            return ret;
        }
        if (multiDisplayParamValidate(params[i],
                                      params[i + 1],
                                      params[i + 2],
                                      params[i + 3],
                                      params[i + 4])) {
            LOG(ERROR) << "Invalid index/width/height/dpi settings for multidisplay command";
            ret.clear();
            return ret;
        }
        ret.emplace(params[i], MultiDisplayInfo(-1, -1, params[i + 1], params[i + 2],
                                                params[i + 3], params[i + 4], true));
    }
    return ret;
}

void MultiDisplay::loadConfig() {
    // Get the multidisplay configs from startup parameters, if yes,
    // override the configs in config.ini
    // This stage happens before the MultiDisplayPipe created (bootCompleted)
    // or restored (snapshot). MultiDisplay configs will not send to guest
    // immediately.
    // For cold boot, MultiDisplayPipe queries configs when it is created.
    // For snapshot, MultiDisplayPipe query will not happen, instead,
    // onLoad() function later may overwrite the multidisplay states to
    // in sync with guest states.
    if (!featurecontrol::isEnabled(android::featurecontrol::MultiDisplay)) {
        return;
    }
    if (android_foldable_any_folded_area_configured()) {
        return;
    }
    if (mGuestMode) {
        return;
    }

    std::map<uint32_t, MultiDisplayInfo> info = parseConfig();
    if (info.size()) {
        LOG(VERBOSE) << "config multidisplay with command-line";
        for (const auto& i : info) {
            setMultiDisplay(i.first,
                            -1,
                            -1,
                            i.second.width,
                            i.second.height,
                            i.second.dpi,
                            i.second.flag,
                            true);
            mWindowAgent->updateUIMultiDisplayPage(i.first);
        }
    } else {
        LOG(VERBOSE) << "config multidisplay with config.ini "
                        << android_hw->hw_display1_width
                        << "x" << android_hw->hw_display1_height << " " <<
                        android_hw->hw_display2_width << "x" <<
                        android_hw->hw_display2_height << " " <<
                        android_hw->hw_display3_width << "x" <<
                        android_hw->hw_display3_height;
        if (android_hw->hw_display1_width != 0 &&
            android_hw->hw_display1_height != 0) {
            LOG(VERBOSE) << " add display 1";
            setMultiDisplay(1,
                            android_hw->hw_display1_xOffset,
                            android_hw->hw_display1_yOffset,
                            android_hw->hw_display1_width,
                            android_hw->hw_display1_height,
                            android_hw->hw_display1_density,
                            android_hw->hw_display1_flag,
                            true);
            mWindowAgent->updateUIMultiDisplayPage(1);
        }
        if (android_hw->hw_display2_width != 0 &&
            android_hw->hw_display2_height != 0) {
            LOG(VERBOSE) << " add display 2";
            setMultiDisplay(2,
                            android_hw->hw_display2_xOffset,
                            android_hw->hw_display2_yOffset,
                            android_hw->hw_display2_width,
                            android_hw->hw_display2_height,
                            android_hw->hw_display2_density,
                            android_hw->hw_display2_flag,
                            true);
            mWindowAgent->updateUIMultiDisplayPage(2);
        }
        if (android_hw->hw_display3_width != 0 &&
            android_hw->hw_display3_height != 0) {
            LOG(VERBOSE) << " add display 3";
            setMultiDisplay(3,
                            android_hw->hw_display3_xOffset,
                            android_hw->hw_display3_yOffset,
                            android_hw->hw_display3_width,
                            android_hw->hw_display3_height,
                            android_hw->hw_display3_density,
                            android_hw->hw_display3_flag,
                            true);
            mWindowAgent->updateUIMultiDisplayPage(3);
        }
    }
}

void MultiDisplay::onSave(base::Stream* stream) {
    AutoLock lock(mLock);
    base::saveCollection(
        stream, mMultiDisplay,
        [](base::Stream* s,
           const std::map<uint32_t, MultiDisplayInfo>::value_type& pair) {
        s->putBe32(pair.first);
        s->putBe32(pair.second.pos_x);
        s->putBe32(pair.second.pos_y);
        s->putBe32(pair.second.width);
        s->putBe32(pair.second.height);
        s->putBe32(pair.second.dpi);
        s->putBe32(pair.second.flag);
        s->putBe32(pair.second.cb);
        s->putByte(pair.second.enabled);
    });
}

void MultiDisplay::onLoad(base::Stream* stream) {
    std::map<uint32_t, MultiDisplayInfo> displaysOnLoad;
    base::loadCollection(stream, &displaysOnLoad,
                         [this](base::Stream* stream) -> std::map<uint32_t, MultiDisplayInfo>::value_type {
        const uint32_t idx = stream->getBe32();
        const int32_t pos_x = stream->getBe32();
        const int32_t pos_y = stream->getBe32();
        const uint32_t width = stream->getBe32();
        const uint32_t height = stream->getBe32();
        const uint32_t dpi = stream->getBe32();
        const uint32_t flag = stream->getBe32();
        const uint32_t cb = stream->getBe32();
        const bool enabled = stream->getByte();
        return {idx, {pos_x, pos_y, width, height, dpi, flag, enabled, cb}};
    });
    // Restore the multidisplays of the snapshot.
    std::set<uint32_t> ids;
    uint32_t combinedDisplayWidth = 0;
    uint32_t combinedDisplayHeight = 0;
    bool activeBeforeLoad, activeAfterLoad;
    {
        AutoLock lock(mLock);
        for (const auto& iter : mMultiDisplay) {
            ids.insert(iter.first);
        }
        for (const auto& iter: displaysOnLoad) {
            ids.insert(iter.first);
        }
        activeBeforeLoad = getNumberActiveMultiDisplaysLocked() > 1;
        mMultiDisplay.clear();
        mMultiDisplay = displaysOnLoad;
        activeAfterLoad = getNumberActiveMultiDisplaysLocked() > 1;
        getCombinedDisplaySizeLocked(&combinedDisplayWidth, &combinedDisplayHeight);
    }
    if (activeAfterLoad) {
        if (!activeBeforeLoad) {
            mWindowAgent->setNoSkin();
        }
        mWindowAgent->setUIDisplayRegion(0, 0, combinedDisplayWidth, combinedDisplayHeight);
    } else {
        if (activeBeforeLoad) {
            mWindowAgent->setUIDisplayRegion(0, 0, combinedDisplayWidth, combinedDisplayHeight);
            mWindowAgent->restoreSkin();
        }
    }
    for (const auto& iter : ids) {
        mWindowAgent->updateUIMultiDisplayPage(iter);
    }
}

}   // namespace android

void android_init_multi_display(const QAndroidEmulatorWindowAgent* const windowAgent,
                                const QAndroidRecordScreenAgent* const recordAgent,
                                bool isGuestMode) {
    android::sMultiDisplay = new android::MultiDisplay(windowAgent, recordAgent, isGuestMode);
}

extern "C" {
void android_load_multi_display_config() {
    if (!android::sMultiDisplay) {
        LOG(ERROR) << "Multidisplay not initiated yet, cannot config";
        return;
    }
    android::sMultiDisplay->loadConfig();
}
}
