1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "EvsStateControl.h" 17 #include "RenderDirectView.h" 18 #include "RenderTopView.h" 19 20 #include <stdio.h> 21 #include <string.h> 22 23 #include <log/log.h> 24 25 26 // TODO: Seems like it'd be nice if the Vehicle HAL provided such helpers (but how & where?) 27 inline constexpr VehiclePropertyType getPropType(VehicleProperty prop) { 28 return static_cast<VehiclePropertyType>( 29 static_cast<int32_t>(prop) 30 & static_cast<int32_t>(VehiclePropertyType::MASK)); 31 } 32 33 34 EvsStateControl::EvsStateControl(android::sp <IVehicle> pVnet, 35 android::sp <IEvsEnumerator> pEvs, 36 android::sp <IEvsDisplay> pDisplay, 37 const ConfigManager& config) : 38 mVehicle(pVnet), 39 mEvs(pEvs), 40 mDisplay(pDisplay), 41 mConfig(config), 42 mCurrentState(OFF) { 43 44 // Initialize the property value containers we'll be updating (they'll be zeroed by default) 45 static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32, 46 "Unexpected type for GEAR_SELECTION property"); 47 static_assert(getPropType(VehicleProperty::TURN_SIGNAL_STATE) == VehiclePropertyType::INT32, 48 "Unexpected type for TURN_SIGNAL_STATE property"); 49 50 mGearValue.prop = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION); 51 mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE); 52 53 #if 0 // This way we only ever deal with cameras which exist in the system 54 // Build our set of cameras for the states we support 55 ALOGD("Requesting camera list"); 56 mEvs->getCameraList([this, &config](hidl_vec<CameraDesc> cameraList) { 57 ALOGI("Camera list callback received %zu cameras", 58 cameraList.size()); 59 for (auto&& cam: cameraList) { 60 ALOGD("Found camera %s", cam.cameraId.c_str()); 61 bool cameraConfigFound = false; 62 63 // Check our configuration for information about this camera 64 // Note that a camera can have a compound function string 65 // such that a camera can be "right/reverse" and be used for both. 66 // If more than one camera is listed for a given function, we'll 67 // list all of them and let the UX/rendering logic use one, some 68 // or all of them as appropriate. 69 for (auto&& info: config.getCameras()) { 70 if (cam.cameraId == info.cameraId) { 71 // We found a match! 72 if (info.function.find("reverse") != std::string::npos) { 73 mCameraList[State::REVERSE].push_back(info); 74 } 75 if (info.function.find("right") != std::string::npos) { 76 mCameraList[State::RIGHT].push_back(info); 77 } 78 if (info.function.find("left") != std::string::npos) { 79 mCameraList[State::LEFT].push_back(info); 80 } 81 if (info.function.find("park") != std::string::npos) { 82 mCameraList[State::PARKING].push_back(info); 83 } 84 cameraConfigFound = true; 85 break; 86 } 87 } 88 if (!cameraConfigFound) { 89 ALOGW("No config information for hardware camera %s", 90 cam.cameraId.c_str()); 91 } 92 } 93 } 94 ); 95 #else // This way we use placeholders for cameras in the configuration but not reported by EVS 96 // Build our set of cameras for the states we support 97 ALOGD("Requesting camera list"); 98 for (auto&& info: config.getCameras()) { 99 if (info.function.find("reverse") != std::string::npos) { 100 mCameraList[State::REVERSE].push_back(info); 101 } 102 if (info.function.find("right") != std::string::npos) { 103 mCameraList[State::RIGHT].push_back(info); 104 } 105 if (info.function.find("left") != std::string::npos) { 106 mCameraList[State::LEFT].push_back(info); 107 } 108 if (info.function.find("park") != std::string::npos) { 109 mCameraList[State::PARKING].push_back(info); 110 } 111 } 112 #endif 113 114 ALOGD("State controller ready"); 115 } 116 117 118 bool EvsStateControl::startUpdateLoop() { 119 // Create the thread and report success if it gets started 120 mRenderThread = std::thread([this](){ updateLoop(); }); 121 return mRenderThread.joinable(); 122 } 123 124 125 void EvsStateControl::postCommand(const Command& cmd) { 126 // Push the command onto the queue watched by updateLoop 127 mLock.lock(); 128 mCommandQueue.push(cmd); 129 mLock.unlock(); 130 131 // Send a signal to wake updateLoop in case it is asleep 132 mWakeSignal.notify_all(); 133 } 134 135 136 void EvsStateControl::updateLoop() { 137 ALOGD("Starting EvsStateControl update loop"); 138 139 bool run = true; 140 while (run) { 141 // Process incoming commands 142 { 143 std::lock_guard <std::mutex> lock(mLock); 144 while (!mCommandQueue.empty()) { 145 const Command& cmd = mCommandQueue.front(); 146 switch (cmd.operation) { 147 case Op::EXIT: 148 run = false; 149 break; 150 case Op::CHECK_VEHICLE_STATE: 151 // Just running selectStateForCurrentConditions below will take care of this 152 break; 153 case Op::TOUCH_EVENT: 154 // TODO: Implement this given the x/y location of the touch event 155 // Ignore for now 156 break; 157 } 158 mCommandQueue.pop(); 159 } 160 } 161 162 // Review vehicle state and choose an appropriate renderer 163 if (!selectStateForCurrentConditions()) { 164 ALOGE("selectStateForCurrentConditions failed so we're going to die"); 165 break; 166 } 167 168 // If we have an active renderer, give it a chance to draw 169 if (mCurrentRenderer) { 170 // Get the output buffer we'll use to display the imagery 171 BufferDesc tgtBuffer = {}; 172 mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc& buff) { 173 tgtBuffer = buff; 174 } 175 ); 176 177 if (tgtBuffer.memHandle == nullptr) { 178 ALOGE("Didn't get requested output buffer -- skipping this frame."); 179 } else { 180 // Generate our output image 181 if (!mCurrentRenderer->drawFrame(tgtBuffer)) { 182 // If drawing failed, we want to exit quickly so an app restart can happen 183 run = false; 184 } 185 186 // Send the finished image back for display 187 mDisplay->returnTargetBufferForDisplay(tgtBuffer); 188 } 189 } else { 190 // No active renderer, so sleep until somebody wakes us with another command 191 std::unique_lock<std::mutex> lock(mLock); 192 mWakeSignal.wait(lock); 193 } 194 } 195 196 ALOGW("EvsStateControl update loop ending"); 197 198 // TODO: Fix it so we can exit cleanly from the main thread instead 199 printf("Shutting down app due to state control loop ending\n"); 200 ALOGE("KILLING THE APP FROM THE EvsStateControl LOOP ON DRAW FAILURE!!!"); 201 exit(1); 202 } 203 204 205 bool EvsStateControl::selectStateForCurrentConditions() { 206 static int32_t sDummyGear = int32_t(VehicleGear::GEAR_REVERSE); 207 static int32_t sDummySignal = int32_t(VehicleTurnSignal::NONE); 208 209 if (mVehicle != nullptr) { 210 // Query the car state 211 if (invokeGet(&mGearValue) != StatusCode::OK) { 212 ALOGE("GEAR_SELECTION not available from vehicle. Exiting."); 213 return false; 214 } 215 if ((mTurnSignalValue.prop == 0) || (invokeGet(&mTurnSignalValue) != StatusCode::OK)) { 216 // Silently treat missing turn signal state as no turn signal active 217 mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1); 218 mTurnSignalValue.prop = 0; 219 } 220 } else { 221 // While testing without a vehicle, behave as if we're in reverse for the first 20 seconds 222 static const int kShowTime = 20; // seconds 223 224 // See if it's time to turn off the default reverse camera 225 static std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); 226 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); 227 if (std::chrono::duration_cast<std::chrono::seconds>(now - start).count() > kShowTime) { 228 // Switch to drive (which should turn off the reverse camera) 229 sDummyGear = int32_t(VehicleGear::GEAR_DRIVE); 230 } 231 232 // Build the dummy vehicle state values (treating single values as 1 element vectors) 233 mGearValue.value.int32Values.setToExternal(&sDummyGear, 1); 234 mTurnSignalValue.value.int32Values.setToExternal(&sDummySignal, 1); 235 } 236 237 // Choose our desired EVS state based on the current car state 238 // TODO: Update this logic, and consider user input when choosing if a view should be presented 239 State desiredState = OFF; 240 if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_REVERSE)) { 241 desiredState = REVERSE; 242 } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::RIGHT)) { 243 desiredState = RIGHT; 244 } else if (mTurnSignalValue.value.int32Values[0] == int32_t(VehicleTurnSignal::LEFT)) { 245 desiredState = LEFT; 246 } else if (mGearValue.value.int32Values[0] == int32_t(VehicleGear::GEAR_PARK)) { 247 desiredState = PARKING; 248 } 249 250 // Apply the desire state 251 return configureEvsPipeline(desiredState); 252 } 253 254 255 StatusCode EvsStateControl::invokeGet(VehiclePropValue *pRequestedPropValue) { 256 StatusCode status = StatusCode::TRY_AGAIN; 257 258 // Call the Vehicle HAL, which will block until the callback is complete 259 mVehicle->get(*pRequestedPropValue, 260 [pRequestedPropValue, &status] 261 (StatusCode s, const VehiclePropValue& v) { 262 status = s; 263 if (s == StatusCode::OK) { 264 *pRequestedPropValue = v; 265 } 266 } 267 ); 268 269 return status; 270 } 271 272 273 bool EvsStateControl::configureEvsPipeline(State desiredState) { 274 if (mCurrentState == desiredState) { 275 // Nothing to do here... 276 return true; 277 } 278 279 ALOGD("Switching to state %d.", desiredState); 280 ALOGD(" Current state %d has %zu cameras", mCurrentState, 281 mCameraList[mCurrentState].size()); 282 ALOGD(" Desired state %d has %zu cameras", desiredState, 283 mCameraList[desiredState].size()); 284 285 // Since we're changing states, shut down the current renderer 286 if (mCurrentRenderer != nullptr) { 287 mCurrentRenderer->deactivate(); 288 mCurrentRenderer = nullptr; // It's a smart pointer, so destructs on assignment to null 289 } 290 291 // Do we need a new direct view renderer? 292 if (mCameraList[desiredState].size() > 1 || desiredState == PARKING) { 293 // TODO: DO we want other kinds of compound view or else sequentially selected views? 294 mCurrentRenderer = std::make_unique<RenderTopView>(mEvs, 295 mCameraList[desiredState], 296 mConfig); 297 if (!mCurrentRenderer) { 298 ALOGE("Failed to construct top view renderer. Skipping state change."); 299 return false; 300 } 301 } else if (mCameraList[desiredState].size() == 1) { 302 // We have a camera assigned to this state for direct view 303 mCurrentRenderer = std::make_unique<RenderDirectView>(mEvs, 304 mCameraList[desiredState][0]); 305 if (!mCurrentRenderer) { 306 ALOGE("Failed to construct direct renderer. Skipping state change."); 307 return false; 308 } 309 } 310 311 // Now set the display state based on whether we have a video feed to show 312 if (mCurrentRenderer == nullptr) { 313 ALOGD("Turning off the display"); 314 mDisplay->setDisplayState(DisplayState::NOT_VISIBLE); 315 } else { 316 // Start the camera stream 317 ALOGD("Starting camera stream"); 318 if (!mCurrentRenderer->activate()) { 319 ALOGE("New renderer failed to activate"); 320 return false; 321 } 322 323 // Activate the display 324 ALOGD("Arming the display"); 325 Return<EvsResult> result = mDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); 326 if (result != EvsResult::OK) { 327 ALOGE("setDisplayState returned an error (%d)", (EvsResult)result); 328 return false; 329 } 330 } 331 332 // Record our current state 333 ALOGI("Activated state %d.", desiredState); 334 mCurrentState = desiredState; 335 336 return true; 337 } 338