/* // Copyright (c) 2014 Intel Corporation // // 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 <HwcTrace.h> #include <IDisplayDevice.h> #include <DisplayQuery.h> #include <BufferManager.h> #include <DisplayPlaneManager.h> #include <Hwcomposer.h> #include <DisplayAnalyzer.h> #include <cutils/properties.h> #include <GraphicBuffer.h> #include <ExternalDevice.h> #include <VirtualDevice.h> namespace android { namespace intel { DisplayAnalyzer::DisplayAnalyzer() : mInitialized(false), mVideoExtModeEnabled(true), mVideoExtModeEligible(false), mVideoExtModeActive(false), mBlankDevice(false), mOverlayAllowed(true), mActiveInputState(true), mIgnoreVideoSkipFlag(false), mProtectedVideoSession(false), mCachedNumDisplays(0), mCachedDisplays(0), mPendingEvents(), mEventMutex(), mEventHandledCondition() { } DisplayAnalyzer::~DisplayAnalyzer() { } bool DisplayAnalyzer::initialize() { // by default video extended mode is enabled char prop[PROPERTY_VALUE_MAX]; if (property_get("hwc.video.extmode.enable", prop, "1") > 0) { mVideoExtModeEnabled = atoi(prop) ? true : false; } mVideoExtModeEligible = false; mVideoExtModeActive = false; mBlankDevice = false; mOverlayAllowed = true; mActiveInputState = true; mIgnoreVideoSkipFlag = false; mProtectedVideoSession = false; mCachedNumDisplays = 0; mCachedDisplays = 0; mPendingEvents.clear(); mVideoStateMap.clear(); mInitialized = true; return true; } void DisplayAnalyzer::deinitialize() { mPendingEvents.clear(); mVideoStateMap.clear(); mInitialized = false; } void DisplayAnalyzer::analyzeContents( size_t numDisplays, hwc_display_contents_1_t** displays) { // cache and use them only in this context during analysis mCachedNumDisplays = numDisplays; mCachedDisplays = displays; handlePendingEvents(); if (mVideoExtModeEnabled) { handleVideoExtMode(); } if (mBlankDevice) { // this will make sure device is blanked after geometry changes. // blank event is only processed once blankSecondaryDevice(); } } void DisplayAnalyzer::handleVideoExtMode() { bool eligible = mVideoExtModeEligible; checkVideoExtMode(); if (eligible == mVideoExtModeEligible) { if (mVideoExtModeActive) { // need to mark all layers setCompositionType(0, HWC_OVERLAY, false); } return; } if (mVideoExtModeEligible) { if (mActiveInputState) { VTRACE("input is active"); } else { enterVideoExtMode(); } } else { exitVideoExtMode(); } } void DisplayAnalyzer::checkVideoExtMode() { if (mVideoStateMap.size() != 1) { mVideoExtModeEligible = false; return; } Hwcomposer *hwc = &Hwcomposer::getInstance(); ExternalDevice *eDev = static_cast<ExternalDevice *>(hwc->getDisplayDevice(IDisplayDevice::DEVICE_EXTERNAL)); VirtualDevice *vDev = static_cast<VirtualDevice *>(hwc->getDisplayDevice(IDisplayDevice::DEVICE_VIRTUAL)); if ((!eDev || !eDev->isConnected()) && (!vDev || !vDev->isFrameServerActive())) { mVideoExtModeEligible = false; return; } bool geometryChanged = false; int activeDisplays = 0; hwc_display_contents_1_t *content = NULL; for (int i = 0; i < (int)mCachedNumDisplays; i++) { content = mCachedDisplays[i]; if (content == NULL) { continue; } activeDisplays++; if (content->flags & HWC_GEOMETRY_CHANGED) { geometryChanged = true; } } if (activeDisplays <= 1) { mVideoExtModeEligible = false; return; } // video state update event may come later than geometry change event. // in that case, video extended mode is not detected properly. #if 0 if (geometryChanged == false) { // use previous analysis result return; } #endif // reset eligibility of video extended mode mVideoExtModeEligible = false; // check if there is video layer in the primary device content = mCachedDisplays[0]; if (content == NULL) { return; } buffer_handle_t videoHandle = 0; bool videoLayerExist = false; bool videoFullScreenOnPrimary = false; bool isVideoLayerSkipped = false; // exclude the frame buffer target layer for (int j = 0; j < (int)content->numHwLayers - 1; j++) { videoLayerExist = isVideoLayer(content->hwLayers[j]); if (videoLayerExist) { if ((content->hwLayers[j].flags & HWC_SKIP_LAYER)) { isVideoLayerSkipped = true; } videoHandle = content->hwLayers[j].handle; videoFullScreenOnPrimary = isVideoFullScreen(0, content->hwLayers[j]); break; } } if (videoLayerExist == false) { // no video layer is found in the primary layer return; } // check whether video layer exists in external device or virtual device // TODO: video may exist in virtual device but no in external device or vice versa // TODO: multiple video layers are not addressed here for (int i = 1; i < (int)mCachedNumDisplays; i++) { content = mCachedDisplays[i]; if (content == NULL) { continue; } // exclude the frame buffer target layer for (int j = 0; j < (int)content->numHwLayers - 1; j++) { if (content->hwLayers[j].handle == videoHandle) { isVideoLayerSkipped |= (content->hwLayers[j].flags & HWC_SKIP_LAYER); VTRACE("video layer exists in device %d", i); if (isVideoLayerSkipped || videoFullScreenOnPrimary){ VTRACE("Video ext mode eligible, %d, %d", isVideoLayerSkipped, videoFullScreenOnPrimary); mVideoExtModeEligible = true; } else { mVideoExtModeEligible = isVideoFullScreen(i, content->hwLayers[j]); } return; } } } } bool DisplayAnalyzer::isVideoExtModeActive() { return mVideoExtModeActive; } bool DisplayAnalyzer::isVideoExtModeEnabled() { #if 1 // enable it for run-time debugging purpose. char prop[PROPERTY_VALUE_MAX]; if (property_get("hwc.video.extmode.enable", prop, "1") > 0) { mVideoExtModeEnabled = atoi(prop) ? true : false; } ITRACE("video extended mode enabled: %d", mVideoExtModeEnabled); #endif return mVideoExtModeEnabled; } bool DisplayAnalyzer::isVideoLayer(hwc_layer_1_t &layer) { bool ret = false; BufferManager *bm = Hwcomposer::getInstance().getBufferManager(); if (!layer.handle) { return false; } DataBuffer *buffer = bm->lockDataBuffer(layer.handle); if (!buffer) { ETRACE("failed to get buffer"); } else { ret = DisplayQuery::isVideoFormat(buffer->getFormat()); bm->unlockDataBuffer(buffer); } return ret; } bool DisplayAnalyzer::isVideoFullScreen(int device, hwc_layer_1_t &layer) { IDisplayDevice *displayDevice = Hwcomposer::getInstance().getDisplayDevice(device); if (!displayDevice) { return false; } int width = 0, height = 0; if (!displayDevice->getDisplaySize(&width, &height)) { return false; } VTRACE("video left %d, right %d, top %d, bottom %d, device width %d, height %d", layer.displayFrame.left, layer.displayFrame.right, layer.displayFrame.top, layer.displayFrame.bottom, width, height); // full-screen defintion: // width of target display frame == width of target device, with 1 pixel of tolerance, or // Height of target display frame == height of target device, with 1 pixel of tolerance, or // width * height of display frame > 90% of width * height of display device, or // any of above condition is met on either primary display or secondary display int dstW = layer.displayFrame.right - layer.displayFrame.left; int dstH = layer.displayFrame.bottom - layer.displayFrame.top; if (abs(dstW - width) > 1 && abs(dstH - height) > 1 && dstW * dstH * 10 < width * height * 9) { VTRACE("video is not full-screen"); return false; } return true; } bool DisplayAnalyzer::isOverlayAllowed() { return mOverlayAllowed; } int DisplayAnalyzer::getVideoInstances() { return (int)mVideoStateMap.size(); } void DisplayAnalyzer::postHotplugEvent(bool connected) { if (!connected) { // enable vsync on the primary device immediately Hwcomposer::getInstance().getVsyncManager()->enableDynamicVsync(true); } // handle hotplug event (vsync switch) asynchronously Event e; e.type = HOTPLUG_EVENT; e.bValue = connected; postEvent(e); Hwcomposer::getInstance().invalidate(); } void DisplayAnalyzer::postVideoEvent(int instanceID, int state) { Event e; e.type = VIDEO_EVENT; e.videoEvent.instanceID = instanceID; e.videoEvent.state = state; postEvent(e); if ((state == VIDEO_PLAYBACK_STARTING) || (state == VIDEO_PLAYBACK_STOPPING && mProtectedVideoSession)) { Hwcomposer::getInstance().invalidate(); mOverlayAllowed = false; hwc_display_contents_1_t *content = NULL; for (int i = 0; i < (int)mCachedNumDisplays; i++) { setCompositionType(i, HWC_FRAMEBUFFER, true); } // wait for up to 100ms until overlay is disabled. int loop = 0; while (loop++ < 6) { if (Hwcomposer::getInstance().getPlaneManager()->isOverlayPlanesDisabled()) break; usleep(16700); } if (loop >= 6) { WTRACE("timeout disabling overlay "); } } } void DisplayAnalyzer::postBlankEvent(bool blank) { Event e; e.type = BLANK_EVENT; e.bValue = blank; postEvent(e); Hwcomposer::getInstance().invalidate(); } void DisplayAnalyzer::postInputEvent(bool active) { Event e; e.type = INPUT_EVENT; e.bValue = active; postEvent(e); Hwcomposer::getInstance().invalidate(); } void DisplayAnalyzer::postIdleEntryEvent(void) { Event e; e.type = IDLE_ENTRY_EVENT; e.nValue = 0; postEvent(e); } void DisplayAnalyzer::postEvent(Event& e) { Mutex::Autolock lock(mEventMutex); mPendingEvents.add(e); } bool DisplayAnalyzer::getEvent(Event& e) { Mutex::Autolock lock(mEventMutex); if (mPendingEvents.size() == 0) { return false; } e = mPendingEvents[0]; mPendingEvents.removeAt(0); return true; } void DisplayAnalyzer::handlePendingEvents() { // handle one event per analysis to avoid blocking surface flinger // some event may take lengthy time to process Event e; if (!getEvent(e)) { return; } switch (e.type) { case HOTPLUG_EVENT: handleHotplugEvent(e.bValue); break; case BLANK_EVENT: handleBlankEvent(e.bValue); break; case VIDEO_EVENT: handleVideoEvent(e.videoEvent.instanceID, e.videoEvent.state); break; case TIMING_EVENT: handleTimingEvent(); break; case INPUT_EVENT: handleInputEvent(e.bValue); break; case DPMS_EVENT: handleDpmsEvent(e.nValue); break; case IDLE_ENTRY_EVENT: handleIdleEntryEvent(e.nValue); break; case IDLE_EXIT_EVENT: handleIdleExitEvent(); break; case VIDEO_CHECK_EVENT: handleVideoCheckEvent(); break; } } void DisplayAnalyzer::handleHotplugEvent(bool connected) { Hwcomposer *hwc = &Hwcomposer::getInstance(); if (connected) { if (mVideoStateMap.size() == 1) { // Some video apps wouldn't update video state again when plugin HDMI // and fail to reset refresh rate ExternalDevice *dev = NULL; dev = (ExternalDevice *)hwc->getDisplayDevice(IDisplayDevice::DEVICE_EXTERNAL); if (!dev || !dev->isConnected()) { ITRACE("External device isn't connected"); return; } if (hwc->getMultiDisplayObserver()->isExternalDeviceTimingFixed()) { VTRACE("Timing of external device is fixed."); return; } VideoSourceInfo info; int instanceID = mVideoStateMap.keyAt(0); status_t err = hwc->getMultiDisplayObserver()->getVideoSourceInfo( instanceID, &info); if (err == NO_ERROR) { int hz = dev->getRefreshRate(); if (hz > 0 && info.frameRate > 0 && hz != info.frameRate) { ITRACE("Old Hz %d, new one %d", hz, info.frameRate); dev->setRefreshRate(info.frameRate); } else WTRACE("Old Hz %d is invalid, %d", hz, info.frameRate); } } } else { if (mVideoStateMap.size() == 1) { // Reset input state if HDMI is plug out to // avoid entering extended mode immediately after HDMI is plug in mActiveInputState = true; } } } void DisplayAnalyzer::handleBlankEvent(bool blank) { mBlankDevice = blank; // force geometry changed in the secondary device to reset layer composition type for (int i = 0; i < (int)mCachedNumDisplays; i++) { if (i == IDisplayDevice::DEVICE_PRIMARY) { continue; } if (mCachedDisplays[i]) { mCachedDisplays[i]->flags |= HWC_GEOMETRY_CHANGED; } } blankSecondaryDevice(); } void DisplayAnalyzer::handleTimingEvent() { // check whether external device is connected, reset refresh rate to match video frame rate // if video is in playing state or reset refresh rate to default preferred one if video is not // at playing state Hwcomposer *hwc = &Hwcomposer::getInstance(); ExternalDevice *dev = NULL; dev = (ExternalDevice *)hwc->getDisplayDevice(IDisplayDevice::DEVICE_EXTERNAL); if (!dev) { return; } if (!dev->isConnected()) { return; } if (hwc->getMultiDisplayObserver()->isExternalDeviceTimingFixed()) { VTRACE("Timing of external device is fixed."); return; } int hz = 0; if (mVideoStateMap.size() == 1) { VideoSourceInfo info; int instanceID = mVideoStateMap.keyAt(0); status_t err = hwc->getMultiDisplayObserver()->getVideoSourceInfo( instanceID, &info); if (err == NO_ERROR) { hz = info.frameRate; } } dev->setRefreshRate(hz); } void DisplayAnalyzer::handleVideoEvent(int instanceID, int state) { mVideoStateMap.removeItem(instanceID); if (state != VIDEO_PLAYBACK_STOPPED) { mVideoStateMap.add(instanceID, state); } Hwcomposer *hwc = &Hwcomposer::getInstance(); // sanity check if (hwc->getMultiDisplayObserver()->getVideoSessionNumber() != (int)mVideoStateMap.size()) { WTRACE("session number does not match!!"); mVideoStateMap.clear(); if (state != VIDEO_PLAYBACK_STOPPED) { mVideoStateMap.add(instanceID, state); } } // check if composition type needs to be reset bool reset = false; if ((state == VIDEO_PLAYBACK_STARTING) || (state == VIDEO_PLAYBACK_STOPPING && mProtectedVideoSession)) { // if video is in starting or stopping stage, overlay use is temporarily not allowed to // avoid scrambed RGB overlay if video is protected. mOverlayAllowed = false; reset = true; } else { reset = !mOverlayAllowed; mOverlayAllowed = true; } if (reset) { hwc_display_contents_1_t *content = NULL; for (int i = 0; i < (int)mCachedNumDisplays; i++) { setCompositionType(i, HWC_FRAMEBUFFER, true); } } if (mVideoStateMap.size() == 0) { // reset active input state after video playback stops. // MDS should update input state in 5 seconds after video playback starts mActiveInputState = true; } mProtectedVideoSession = false; if (state == VIDEO_PLAYBACK_STARTED) { VideoSourceInfo info; hwc->getMultiDisplayObserver()->getVideoSourceInfo( getFirstVideoInstanceSessionID(), &info); mProtectedVideoSession = info.isProtected; } // Setting timing immediately, // Don't posthone to next circle handleTimingEvent(); handleVideoCheckEvent(); } void DisplayAnalyzer::blankSecondaryDevice() { hwc_display_contents_1_t *content = NULL; hwc_layer_1 *layer = NULL; for (int i = 0; i < (int)mCachedNumDisplays; i++) { if (i == IDisplayDevice::DEVICE_PRIMARY) { continue; } content = mCachedDisplays[i]; if (content == NULL) { continue; } for (int j = 0; j < (int)content->numHwLayers - 1; j++) { layer = &content->hwLayers[j]; if (!layer) { continue; } if (mBlankDevice) { layer->hints |= HWC_HINT_CLEAR_FB; layer->flags &= ~HWC_SKIP_LAYER; layer->compositionType = HWC_OVERLAY; } else { layer->hints &= ~HWC_HINT_CLEAR_FB; layer->compositionType = HWC_FRAMEBUFFER; } } } } void DisplayAnalyzer::handleInputEvent(bool active) { if (active == mActiveInputState) { WTRACE("same input state: %d", active); } mActiveInputState = active; if (!mVideoExtModeEligible) { ITRACE("not eligible for video extended mode"); return; } if (active) { exitVideoExtMode(); } else { enterVideoExtMode(); } } void DisplayAnalyzer::handleDpmsEvent(int delayCount) { if (mActiveInputState || !mVideoExtModeEligible) { ITRACE("aborting display power off in video extended mode"); return; } if (delayCount < DELAY_BEFORE_DPMS_OFF) { Event e; e.type = DPMS_EVENT; e.nValue = delayCount + 1; postEvent(e); Hwcomposer::getInstance().invalidate(); return; } if (Hwcomposer::getInstance().getVsyncManager()->getVsyncSource() == IDisplayDevice::DEVICE_PRIMARY) { Hwcomposer::getInstance().getDrm()->setDpmsMode( IDisplayDevice::DEVICE_PRIMARY, IDisplayDevice::DEVICE_DISPLAY_STANDBY); ETRACE("primary display is source of vsync, we only dim backlight"); return; } // panel can't be powered off as touch panel shares the power supply with LCD. DTRACE("primary display coupled with touch on Saltbay, only dim backlight"); Hwcomposer::getInstance().getDrm()->setDpmsMode( IDisplayDevice::DEVICE_PRIMARY, IDisplayDevice::DEVICE_DISPLAY_STANDBY); //IDisplayDevice::DEVICE_DISPLAY_OFF); return; } void DisplayAnalyzer::handleIdleEntryEvent(int count) { DTRACE("handling idle entry event, count %d", count); if (hasProtectedLayer()) { ITRACE("Ignoring idle entry as protected layer exists."); setCompositionType(0, HWC_FRAMEBUFFER, true); return; } // stop idle entry if external device is connected if (mCachedDisplays && mCachedDisplays[IDisplayDevice::DEVICE_EXTERNAL]) { ITRACE("Ignoring idle entry as external device is connected."); setCompositionType(0, HWC_FRAMEBUFFER, true); return; } // stop idle entry if video playback is active // TODO: remove this check for Annidale if (mVideoStateMap.size() > 0) { ITRACE("Ignoring idle entry as video session is active."); setCompositionType(0, HWC_FRAMEBUFFER, true); return; } setCompositionType(0, HWC_FORCE_FRAMEBUFFER, true); // next prepare/set will exit idle state. Event e; e.type = IDLE_EXIT_EVENT; postEvent(e); } void DisplayAnalyzer::handleIdleExitEvent() { DTRACE("handling idle exit event"); setCompositionType(0, HWC_FRAMEBUFFER, true); } void DisplayAnalyzer::handleVideoCheckEvent() { // check if the first seen video layer on secondary device (HDMI/WFD) is marked as skipped // it is assumed video is always skipped if the first seen video layer is skipped // this is to workaround secure video layer transmitted over non secure output // and HWC_SKIP_LAYER set during rotation animation. mIgnoreVideoSkipFlag = false; if (mVideoStateMap.size() != 1 || mCachedNumDisplays <= 1) { return; } intptr_t videoHandles[mCachedNumDisplays]; for (int i = 0; i < (int)mCachedNumDisplays; i++) { videoHandles[i] = 0; hwc_display_contents_1_t *content = mCachedDisplays[i]; if (content == NULL) { continue; } for (int j = 0; j < (int)content->numHwLayers - 1; j++) { if (isVideoLayer(content->hwLayers[j])) { videoHandles[i] = (intptr_t)content->hwLayers[j].handle; if (i > 0) { mIgnoreVideoSkipFlag = !(content->hwLayers[j].flags & HWC_SKIP_LAYER); ITRACE("Ignoring video HWC_SKIP_LAYER: %d on output %d", mIgnoreVideoSkipFlag, i); return; } break; } } } if (videoHandles[0]) { WTRACE("Video is on the primary panel only"); return; } // video state map indicates video session is active and there is secondary // display, need to continue checking as video is not found in the buffers yet Event e; e.type = VIDEO_CHECK_EVENT; postEvent(e); } void DisplayAnalyzer::enterVideoExtMode() { if (mVideoExtModeActive) { WTRACE("already in video extended mode."); return; } ITRACE("entering video extended mode..."); mVideoExtModeActive = true; Hwcomposer::getInstance().getVsyncManager()->resetVsyncSource(); setCompositionType(0, HWC_OVERLAY, true); // Do not power off primary display immediately as flip is asynchronous Event e; e.type = DPMS_EVENT; e.nValue = 0; postEvent(e); Hwcomposer::getInstance().invalidate(); } void DisplayAnalyzer::exitVideoExtMode() { if (!mVideoExtModeActive) { WTRACE("Not in video extended mode"); return; } ITRACE("exiting video extended mode..."); mVideoExtModeActive = false; Hwcomposer::getInstance().getDrm()->setDpmsMode( IDisplayDevice::DEVICE_PRIMARY, IDisplayDevice::DEVICE_DISPLAY_ON); Hwcomposer::getInstance().getVsyncManager()->resetVsyncSource(); setCompositionType(0, HWC_FRAMEBUFFER, true); } bool DisplayAnalyzer::isPresentationLayer(hwc_layer_1_t &layer) { if (layer.handle == NULL) { return false; } if (mCachedDisplays == NULL) { return false; } // check if the given layer exists in the primary device hwc_display_contents_1_t *content = mCachedDisplays[0]; if (content == NULL) { return false; } for (size_t i = 0; i < content->numHwLayers - 1; i++) { if (content->hwLayers[i].handle == layer.handle) { VTRACE("Layer exists for Primary device"); return false; } } return true; } bool DisplayAnalyzer::hasProtectedLayer() { DataBuffer * buffer = NULL; hwc_display_contents_1_t *content = NULL; BufferManager *bm = Hwcomposer::getInstance().getBufferManager(); if (bm == NULL){ return false; } if (mCachedDisplays == NULL) { return false; } // check if the given layer exists in the primary device for (int index = 0; index < (int)mCachedNumDisplays; index++) { content = mCachedDisplays[index]; if (content == NULL) { continue; } for (size_t i = 0; i < content->numHwLayers - 1; i++) { if (isProtectedLayer(content->hwLayers[i])) return true; } } return false; } bool DisplayAnalyzer::isProtectedLayer(hwc_layer_1_t &layer) { if (!layer.handle) { return false; } bool ret = false; BufferManager *bm = Hwcomposer::getInstance().getBufferManager(); DataBuffer *buffer = bm->lockDataBuffer(layer.handle); if (!buffer) { ETRACE("failed to get buffer"); } else { ret = GraphicBuffer::isProtectedBuffer((GraphicBuffer*)buffer); bm->unlockDataBuffer(buffer); } return ret; } bool DisplayAnalyzer::ignoreVideoSkipFlag() { return mIgnoreVideoSkipFlag; } void DisplayAnalyzer::setCompositionType(hwc_display_contents_1_t *display, int type) { for (size_t i = 0; i < display->numHwLayers - 1; i++) { hwc_layer_1_t *layer = &display->hwLayers[i]; if (layer) layer->compositionType = type; } } void DisplayAnalyzer::setCompositionType(int device, int type, bool reset) { hwc_display_contents_1_t *content = mCachedDisplays[device]; if (content == NULL) { ETRACE("Invalid device %d", device); return; } // don't need to set geometry changed if layers are just needed to be marked if (reset) { content->flags |= HWC_GEOMETRY_CHANGED; } setCompositionType(content, type); } int DisplayAnalyzer::getFirstVideoInstanceSessionID() { if (mVideoStateMap.size() >= 1) { return mVideoStateMap.keyAt(0); } return -1; } } // namespace intel } // namespace android