| /* |
| * 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(); |
| } |
| } |